This example is a server driven app built with Parcel and React Server Components. In this setup, routing happens on the server, delivering HTML on initial page load, and client side rendering on subsequent navigations. It also demonstrates React Server Actions to perform mutations, both by calling as a function and as the target of an HTML form.
The example consists of the following main files:
This is the main server entrypoint, built using Express. It is the entry of the Parcel build. All other client and server dependencies are discovered from here.
This is the entry React Server Component that renders the root <html>
element, server content, and any client components. It is marked with the Parcel-specific "use server-entry"
directive, which creates a code splitting entrypoint. Common dependencies between pages are extracted into shared bundles.
This is the main client entrypoint, imported from each page. It uses the Parcel-specific "use client-entry"
directive to mark that it should only run on the client, and not on the server (even during SSR). The client is responsible for hydrating the initial page, and intercepting link clicks and navigations to perform client side routing.
This is a server actions file. Functions exported by this file can be imported from the client and called to send data to the server for processing. It is marked using the "use server"
directive. When Parcel sees this directive, it places the actions into the server bundle, and creates a proxy module on the client that calls the server action via the handler in client.tsx
.
Currently, server actions must be defined in a separate file. Inline server actions (e.g. "use server"
inside a function) are not supported by Parcel.
These are client components. <TodoItem>
renders a todo list item, and uses server actions and useOptimistic
to implement the checkbox and remove buttons. Dialog.tsx
renders a dialog component using client APIs, and accepts the create todo form (which is a server component) as children.
The flow of initial rendering starts on the server.
The server handles routing using Express. When a route handler is called, it calls renderRequest
from @parcel/rsc/node
. This renders the provided component to HTML or an RSC payload depending on the Accept
header. The initial HTML embeds the RSC payload so it can be hydrated without additional network requests.
To hydrate the initial page, the client calls hydrate
from @parcel/rsc/client
. This uses the RSC payload embedded into the initial HTML to make the page interactive. hydrate
returns an updateRoot
function that allows updating the page during client side navigations.
The client also includes a very simple router, allowing subsequent navigations after the initial page load to maintain client state without reloading the full HTML page.
The client listens for the click
event for all link elements on the page using event delegation, as well as the popstate
event to detect when the user navigates with the browser back button. To perform a navigation:
- Use
fetchRSC
from@parcel/rsc/client
to fetch a new RSC payload from the server. This is a simplefetch
wrapper, with all of the options that the browser supports. - Call the
updateRoot
function created during the initial render to update the page. - Once the new page is finished loading, push the new URL to the browser's history with
history.pushState
.
These steps can be customized as needed for your server setup, e.g. using a better client side router, or adding authentication headers.
The server handles fetch requests for RSC payloads using the same route handlers as for HTML. fetchRSC
sets the Accept
header to text/x-component
, and renderRequest
returns an RSC payload instead of HTML.
Server functions allow the client to call the server to perform mutations and other actions. There are two ways server actions can be called: by calling an action function from the client, or by submitting an HTML form.
When a server action is called, the client is responsible for sending a request to the server. This is done by passing a callServer
callback to hydrate
. When a server action proxy function generated by Parcel is called on the client, this handler will be invoked with the id of the action, and the arguments to pass to it.
- Create a
POST
request usingfetchRSC
, and set thersc-action-id
header to the id of the action to call. React will encode the arguments to the action using the RSC protocol. - The server will return a new component to render, along with the return value of the server action.
- Call
updateRoot
to update the page with the component returned by the server. - Return the result of the server action. This will be returned by the proxy function originally called in the client component.
These steps can be customized as needed for your server setup, e.g. adding authentication headers.
When the POST
request handler is called, the server performs the following steps:
- Get the
rsc-action-id
header and usecallAction
from@parcel/rsc/node
to call the server function. - Respond to the HTTP request by rendering the server component, following the steps above, and passing back the promise returned by the action as the result. This will be returned as the result of the action on the client.