AgentSkillsCN

Canva Practices

Canva 实践

SKILL.md

Canva Engineering Practices

Best practices from Canva's engineering culture. Sources: Canva Engineering Blog, Tech Talks

Core Principles

1. Design-First Development

Always start with design, not code:

code
┌─────────────────────────────────────────────────────────────┐
│                 DESIGN-FIRST FLOW                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   User Need → Design Exploration → Prototype → Validate     │
│                                         ↓                   │
│                                    Build Only               │
│                                  After Validation           │
│                                                             │
│   "The best code is code you never had to write"           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Questions before coding:

  • What problem are we solving?
  • Who are we solving it for?
  • How will we know it's solved?
  • Is there a simpler solution?

2. Component-Driven Development

Build reusable, composable components:

typescript
// Canva's component principles

// 1. Single Responsibility
// BAD
const UserCard = ({ user, onEdit, onDelete, onShare }) => {...}

// GOOD
const UserAvatar = ({ src, name }) => {...}
const UserInfo = ({ name, email }) => {...}
const ActionMenu = ({ actions }) => {...}

// 2. Composition over Configuration
// BAD - prop explosion
<Button primary large disabled loading icon="save" />

// GOOD - composable
<Button variant="primary" size="large">
  <LoadingSpinner />
  <Icon name="save" />
  Save
</Button>

// 3. Sensible Defaults
const Button = ({
  variant = 'secondary',
  size = 'medium',
  type = 'button',
  ...props
}) => {...}

3. Performance at Scale

Canva serves billions of designs:

Image Optimization

typescript
// Lazy loading with intersection observer
const LazyImage = ({ src, alt }) => {
  const [isVisible, ref] = useIntersectionObserver();

  return (
    <div ref={ref}>
      {isVisible ? (
        <img src={src} alt={alt} loading="lazy" />
      ) : (
        <Placeholder />
      )}
    </div>
  );
};

// Responsive images
<picture>
  <source srcset="image-400.webp 400w, image-800.webp 800w" type="image/webp" />
  <source srcset="image-400.jpg 400w, image-800.jpg 800w" type="image/jpeg" />
  <img src="image-400.jpg" alt="..." loading="lazy" />
</picture>

Bundle Optimization

typescript
// Code splitting by route
const Editor = lazy(() => import('./Editor'));
const Dashboard = lazy(() => import('./Dashboard'));

// Feature-based splitting
const AdvancedTools = lazy(() =>
  import(/* webpackChunkName: "advanced-tools" */ './AdvancedTools')
);

Render Performance

typescript
// Virtualization for large lists
import { FixedSizeList } from 'react-window';

const DesignList = ({ designs }) => (
  <FixedSizeList
    height={600}
    itemCount={designs.length}
    itemSize={200}
  >
    {({ index, style }) => (
      <DesignCard design={designs[index]} style={style} />
    )}
  </FixedSizeList>
);

// Memoization
const ExpensiveComponent = memo(({ data }) => {
  const processed = useMemo(() => heavyComputation(data), [data]);
  return <div>{processed}</div>;
});

4. Internationalization (i18n)

Canva supports 100+ languages:

typescript
// 1. Never hardcode strings
// BAD
<button>Save</button>

// GOOD
<button>{t('actions.save')}</button>

// 2. Handle pluralization
t('items.count', { count: items.length })
// en: "1 item" / "5 items"
// ru: "1 элемент" / "2 элемента" / "5 элементов"

// 3. Handle RTL languages
const styles = {
  marginLeft: isRTL ? 0 : spacing,
  marginRight: isRTL ? spacing : 0,
  // Better: use logical properties
  marginInlineStart: spacing,
};

// 4. Format dates/numbers locally
new Intl.DateTimeFormat(locale).format(date)
new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)

5. Accessibility (a11y)

Design for everyone:

typescript
// 1. Semantic HTML
// BAD
<div onClick={handleClick}>Click me</div>

// GOOD
<button onClick={handleClick}>Click me</button>

// 2. ARIA when needed
<div
  role="tabpanel"
  aria-labelledby="tab-1"
  aria-hidden={!isActive}
>

// 3. Keyboard navigation
const handleKeyDown = (e) => {
  switch (e.key) {
    case 'ArrowRight': focusNext(); break;
    case 'ArrowLeft': focusPrev(); break;
    case 'Enter':
    case ' ': select(); break;
    case 'Escape': close(); break;
  }
};

// 4. Focus management
const Modal = ({ isOpen, onClose, children }) => {
  const firstFocusRef = useRef();

  useEffect(() => {
    if (isOpen) firstFocusRef.current?.focus();
  }, [isOpen]);

  return (
    <FocusTrap>
      <div role="dialog" aria-modal="true">
        <button ref={firstFocusRef} onClick={onClose}>
          Close
        </button>
        {children}
      </div>
    </FocusTrap>
  );
};

// 5. Color contrast
// Minimum 4.5:1 for normal text
// Minimum 3:1 for large text
// Don't convey info by color alone

6. Collaboration Features

Building for real-time collaboration:

typescript
// Optimistic updates
const updateDesign = async (change) => {
  // 1. Apply immediately (optimistic)
  dispatch({ type: 'APPLY_CHANGE', change });

  try {
    // 2. Sync to server
    await api.saveChange(change);
  } catch (error) {
    // 3. Rollback on failure
    dispatch({ type: 'ROLLBACK_CHANGE', change });
    showError('Failed to save');
  }
};

// Conflict resolution (Last Write Wins with Transform)
const resolveConflict = (localChange, remoteChange) => {
  if (remoteChange.timestamp > localChange.timestamp) {
    return transform(localChange, remoteChange);
  }
  return localChange;
};

// Presence indicators
const Cursors = ({ collaborators }) => (
  <>
    {collaborators.map(user => (
      <Cursor
        key={user.id}
        position={user.cursor}
        color={user.color}
        name={user.name}
      />
    ))}
  </>
);

7. Feature Flags

Safe rollouts:

typescript
// Feature flag usage
const NewEditor = () => {
  const { isEnabled } = useFeatureFlag('new-editor-v2');

  if (!isEnabled) {
    return <OldEditor />;
  }

  return <EditorV2 />;
};

// Gradual rollout
const flagConfig = {
  'new-editor-v2': {
    enabled: true,
    rolloutPercentage: 10, // 10% of users
    allowlist: ['beta-testers'],
    blocklist: ['enterprise-critical'],
  }
};

// Kill switch
if (await checkKillSwitch('new-editor-v2')) {
  return <FallbackUI />;
}

8. Observability

Monitor everything:

typescript
// Structured logging
logger.info('design.saved', {
  designId: design.id,
  userId: user.id,
  duration: endTime - startTime,
  size: design.elements.length,
});

// Performance metrics
performance.mark('render-start');
// ... render ...
performance.mark('render-end');
performance.measure('render-time', 'render-start', 'render-end');

// Error tracking with context
Sentry.withScope(scope => {
  scope.setUser({ id: user.id });
  scope.setTag('feature', 'editor');
  scope.setContext('design', { id: design.id, type: design.type });
  Sentry.captureException(error);
});

Quick Reference

PracticeKey Principle
Design-FirstValidate before building
ComponentsComposable, single-purpose
PerformanceLazy load, virtualize, memoize
i18nNo hardcoded strings
a11ySemantic HTML, keyboard nav
CollaborationOptimistic updates
Feature FlagsGradual, safe rollouts
ObservabilityLog everything