Component architecture

Understand the runtime behavior and limitations of code components
Private Beta

Code components run as isolated React applications within a Webflow page. Understanding their architecture helps you build components that work reliably and efficiently.

How code components work

Code components are React components that render in isolated Shadow DOM containers on Webflow sites. Each component runs in its own React root, which affects how you handle state, rendering, and component communication.

Key concepts

  • Client-side rendering only - components render after page load
  • Isolated React roots - each component runs independently
  • No shared React context - state management requires alternative approaches
  • Shadow DOM encapsulation - styles and DOM are isolated

Client-side rendering

Code components render only in the browser after the page loads. This affects how you handle data fetching and component initialization. This means:

  • Components render after page load - users see a brief loading state
  • All API calls happen in the browser - use useEffect or event handlers
  • No server-side data fetching - fetch data when components mount
  • Authentication tokens are visible - only use public APIs or secure client-side auth

Safe API patterns for data fetching

Use client-side patterns for data fetching:

MyComponent.tsx
1import React, { useEffect, useState } from 'react';
2
3// Define the expected API response structure
4interface ApiResponse {
5 message: string;
6}
7
8export const MyComponent = () => {
9 const [data, setData] = useState<ApiResponse | null>(null);
10 const [loading, setLoading] = useState(true);
11
12 useEffect(() => {
13
14 // Fetch data when component mounts
15 fetch('/api/public-data')
16 .then(response => response.json())
17 .then((data: ApiResponse) => {
18 setData(data);
19 setLoading(false);
20 })
21 .catch(error => {
22 console.error('Failed to fetch data:', error);
23 setLoading(false);
24 });
25 }, []);
26
27 // Show loading state while fetching
28 if (loading) return <div>Loading...</div>;
29
30 // Show message if no data` is available
31 if (!data) return <div>No data available</div>;
32
33 // Render the API response
34 return <div>{data.message}</div>;
35};

Isolated React roots

Each code component runs in its own React root, completely isolated from other components and the main page. This affects how you handle state and communicate between components.

State management constraints

Since components run in separate React roots:

  • No shared React Context - Context providers don’t work across components
  • Independent component lifecycles - each component mounts/unmounts separately
  • No direct state sharing - components can’t directly access each other’s state

State management solutions

Use these patterns to share state between components:

URL parameters

Use the browser’s URLSearchParams API to store state in the URL. This pattern is useful for search queries, filters, or navigation state that should be shareable via URL.

Browser storage

Use localStorage for persistent data or sessionStorage for session-only data. Only store non-sensitive information since this data is visible to users.

Nano Stores

Nano stores is a small, fast, and type-safe state management library for React, and is a useful alternative to React Context for sharing state between components.

To install Nano Stores, run:

$npm install nanostores @nanostores/react

In this example, the Counter and Clicker components share state for the $counter using a Nano store.

1import { atom } from 'nanostores';
2
3// Shared state store - any component can read/write to this
4export const $counter = atom(0);
  • Store.tsx defines the $counter store, which any component can access.
  • Counter.tsx subscribes to the store using useStore() and displays the current count.
  • Clicker.tsx is a button that increments the shared counter by calling $counter.set().

Component communication

For components that need to interact beyond shared state, use the browser’s CustomEvent API to communicate across React roots:

1import React from 'react';
2
3// Theme toggle component dispatches events
4export const ThemeToggle = () => {
5 const handleClick = () => {
6 // Dispatch custom event that other components can listen to
7 window.dispatchEvent(new CustomEvent('theme-changed', {
8 detail: { theme: 'dark' }
9 }));
10 };
11
12 return <button onClick={handleClick}>Switch to Dark Mode</button>;
13 };
14

Shadow DOM encapsulation

Code components run in a Shadow DOM container, which means:

  • Styles are isolated - Component styles don’t affect the main page’s styles
  • DOM is isolated - Component DOM elements don’t affect the main page’s DOM

This means you need to explicitly connect to external styles like site variables, inherited properties, or tag selectors. Learn more about styling components.

Current limitations

  • Single root element required - components must have one root element (no React fragments)
  • Library compatibility - some libraries with multiple entry points may not work correctly
  • Server-side rendering - currently client-side only (planned for future releases)