React Server Components overview
Hydrogen 2.0 is out now. These archival Hydrogen 1.0 docs are provided only to assist developers during their upgrade process. Please migrate as soon as possible.
Hydrogen is modelled after React Server Components, an approach that offers an opinionated data-fetching and rendering workflow for React apps.
This guide provides information about how React Server Components work in the context of Hydrogen.
Note: Hydrogen's implementation of server components is a modified version of React Server Components, which are currently in Alpha. Shopify provides a layer of abstractions to make server components stable for use in Hydrogen apps.
Shopify is currently working with Vercel and the React team to align on enhancements to server components, and will release a future version of Hydrogen with tools for migrating existing Hydrogen apps.
How React Server Components work
React Server Components allow the server and the client to work together to render your Hydrogen app.
For example, the following React element tree is composed of React components that render other React components. React Server Components allow some components to render on the server, some to render in the browser or on the server using server-side rendering (SSR), and others to render on both the server and the client:
Note: You can't import a server component into a client component. However, you can pass a server component into a client component using passthrough props.
Component types
React Server Components include the following component types:
Type | Description | Filename convention |
---|---|---|
Server | Components that fetch data and render content on the server. Their dependencies aren't in the client bundle. Server components don't include any client-side interactivity. Only server components can make calls to the Storefront API. | Server components end in .server.jsx . |
Client | Components that render on the client. Client components include client-side stateful interactivity. | Client components end in .client.jsx . |
Shared | Components that render on both the server and the client. | Shared components don't end in either .client.jsx or .server.jsx . |
Benefits
React Server Components separate client and server logic. This separation provides the following benefits to Hydrogen apps:
- Server-only code that has no impact on bundle size and reduces bundle sizes
- Server-side access to custom and private server-side data sources
- Seamless integration and a well-defined protocol for server and client components
- Streaming rendering and progressive hydration
- Subtree and component-level updates that preserve client state
- Server and client code sharing, where appropriate
Constraints
Tip: You don't need to memorize the rules referenced in this section to use React Server Components. Hydrogen has lint rules and error messages to help enforce the constraints on
.server.jsx
and.client.jsx
files during the rendering process.
React Server Components have the following constraints on server and client components:
- Client components can’t access server-only features, like the filesystem, and can only import other client components.
- Server components can’t access client-only features, like state.
Due to these constraints, there are specific rules that you need to follow when building your Hydrogen app:
Composition
One of the key constraints of React Server Components is that you can't import and render server components from client components. However, you can compose React Server Components to take in props.
The following example shows how to pass a server component as a children
prop to a client component. The OuterServerComponent
can then instantiate both the client and server components. This is how you can have server components under client components in your React element tree.
// MyClientComponent.client.jsx
export default function MyClientComponent({children}) {
return (
<div>
<h1>This code is rendered on the client</h1>
{children}
</div>
);
}
// MyServerComponent.server.jsx
export default function MyServerComponent() {
return <span>This code is rendered on the server</span>;
}
// MyOuterServerComponent.server.jsx
// `MyOuterServerComponent` can instantiate both the client and server
// components. You can pass in `<MyServerComponent/>` as
// the `children` prop to `MyClientComponent`.
import MyClientComponent from './MyClientComponent.client';
import MyServerComponent from './MyServerComponent.server';
export default function MyOuterServerComponent() {
return (
<MyClientComponent>
<MyServerComponent />
</MyClientComponent>
);
}
Sending props
When you send props to client components from a server component, make sure that the props are JSON-serializable. For example, functions or callbacks can't be passed as props.
The following prop would send successfully:
// App.server.jsx
<MyClientComponent color="red" intro={<p>Here's my favorite color:</p>}>
Great to have you here today.
</MyClientComponent>
The following prop wouldn't send successfully:
// App.server.jsx
<MyClientComponent onClick={() => console.log('uh oh')}>
Great to have you here today.
</MyClientComponent>
Sharing code between server and client
In addition to server-specific and client-specific components, you can create components that work on both the server and the client. This allows logic to be shared across environments, as long as the components meet all the constraints of both the server and client components.
Although shared components have the most constraints, many components already obey these rules and can be used across the server and client without modification. For example, many components transform some props based on certain conditions, without using state or loading additional data. This is why shared components are the default and don’t have a dedicated file extension.
Client components and server-side rendering
Client components ending in .client.jsx
are rendered in the browser. However, they are also rendered on the server during server-side rendering (SSR). This is because SSR produces an HTML "preview" of what will eventually be rendered in the browser.
This behavior might be confusing, because the word "client" indicates a client-only behavior. Shopify is working with the React team to refine these naming conventions to make it less confusing.
In the meantime, avoid including browser-only logic in client components in a way that will cause problems during SSR:
// Button.client.jsx
// ❌ Don't do this because `window` isn't available during SSR
function Button() {
const innerWidth = window.innerWidth;
return <button>...</button>
}
// ✅ Do this because `useEffect` doesn't run during SSR
function Button() {
const [innerWidth, setInnerWidth] = useState();
useEffect(() => {
setInnerWidth(window.innerWidth);
}, []);
return <button>...</button>
}
Component organization and index files
You might be familiar with a "facade file" pattern, where similar files are re-exported from a shared index.js
file in a folder. This pattern isn't supported in React Server Components when mixing client components with server components.
If you want to use the facade pattern, then you need to create separate files for client components and server components:
// components/index.js
// ❌ Don't do this because it mixes client components and server components:
export {Button} from './Button.client.jsx'
export {Dropdown} from './Dropdown.client.jsx'
export {Widget} from './Widget.server.jsx'
// components/index.js
// ✅ Do this because only client components are exported
export {Button} from './Button.client.jsx'
export {Dropdown} from './Dropdown.client.jsx'
// components/server.js
// ✅ Do this because only server components are exported
export {Widget} from './Widget.server.jsx'
Next steps
- Learn how to work with React Server Components.
- Improve your app's loading performance with streaming SSR and Suspense.