| | |

Parcel 14 ๐Ÿ“ฆ React Server Components โ€” react-server template & RSC support

โฑ๏ธ Estimated read: 11 min | |

React Server Components (RSC) support is Parcel’s most significant recent addition, introduced as beta in Parcel v2.14.0 . Unlike other RSC implementations that are tied to a full framework, Parcel’s approach is explicitly not a framework. It provides the bundler-level primitivesโ€”the "use client" and "use server" directives, the @parcel/rsc runtime library, and the unified module graph across server and client environmentsโ€”and leaves the routing, server setup, and application structure to the developer . This gives teams the freedom to integrate RSC into an existing client-rendered app without a rewrite, or to build a server-rendered or statically-rendered app from scratch.

This chapter covers the three rendering modes Parcel supports (client-integrated, server-rendered, and static), the react-server template that scaffolds a TypeScript RSC project, the "use server-entry" directive that marks a page entry point, and the @parcel/rsc library that provides the server-side rendering and client-side hydration helpers.

Key point: Parcel is not a framework. It does not provide a router, a server, or a convention for file-based routing. It provides the bundler primitives that make RSC work, and the @parcel/rsc library that wraps the lower-level React APIs. The developer provides the server (Express, Fastify, etc.) and the routing logic.


Why Parcel’s RSC support is different

The framework problem. Most RSC implementations are embedded in a framework: Next.js, Remix, or a meta-framework that provides the server, the router, and the file conventions. The developer adopts the framework to get the RSC benefits. Parcel’s documentation states the philosophy directly: “Unlike other RSC implementations, Parcel is not a full framework. Instead, it provides the tools you need to build your own application or framework from scratch with much less work” .

The unified module graph problem. Parcel v2 has supported multiple environments (browser, node, web worker) since its initial release. The RSC support reuses this infrastructure: the server and client code are part of the same module graph, and the bundler can perform code splitting across environments . This is the technical foundation that makes the "use client" directive work: when the server environment imports a module with "use client", Parcel creates a client reference and a parallel client bundle, and the reference is serialized into the RSC Payload .

The incremental adoption problem. A team that already has a client-rendered React app can add a server target alongside the existing client target. Parcel builds both together and shares the common dependencies. The new feature that benefits from RSC can be rendered on the server, while the rest of the app remains client-rendered . The integration does not require a full migration.


a. The three rendering modes

Parcel supports three modes, and the choice depends on whether the page contains dynamic data that is personalized for the user.

Client-integrated RSC. The existing client-rendered app continues to work as before. A new server target is added, and the server renders specific routes to an RSC Payload. The client fetches the payload and renders it inside a <Suspense> boundary. This mode is for adding RSC to an existing SPA without rewriting it .

Server-rendered RSC. The server is the entry point of the Parcel build, not an HTML file. The server renders the page to an RSC Payload as part of the initial request, which avoids the network waterfall of a client-rendered app. Parcel v2.14.0 includes out-of-the-box support for running the Node server in development mode: parcel src/server.js builds the server and runs it with hot reloading .

Static rendering. The page is pre-rendered to fully static HTML at build time. A marketing page or a blog post that does not contain personalized data is a candidate. The static HTML is served from a CDN, and the interactive Client Components are hydrated in the browser. Parcel includes a built-in static site generator powered by RSC, and the react-static target name is the configuration mechanism .

The three modes can be mixed in the same app by creating multiple targets. A marketing page can be statically rendered to a CDN, and a product dashboard can be server-rendered .


b. The react-server template

The react-server template scaffolds a TypeScript RSC application. The create-parcel CLI is the tool that generates the project.

npm create parcel react-server my-rsc-app

The template creates a project with the client and server targets already configured, the server entry point, and the "use server-entry" directive in the page component . The template is the fastest way to see the RSC setup in action, and it is the starting point for a new project that wants the server-rendered mode.

The package.json targets are the core of the configuration. The client target points at the HTML file, and the server target points at the server code. Parcel builds both together .

{
  "targets": {
    "client": { "source": "index.html" },
    "server": { "source": "src/server.js" }
  }
}

The react-static target name is the configuration for the static mode. The source points at a directory of page components, and each component is rendered to an HTML file in the dist directory .


c. The "use server-entry" directive and the @parcel/rsc library

The "use server-entry" directive is Parcel-specific. It marks a server component as the entry point of a page or route, and it creates a code-splitting boundary. The dependencies referenced by the route are bundled together, and the shared dependencies between pages are placed in a shared bundle . The directive is the mechanism that tells Parcel which server component is the root of the page.

The @parcel/rsc library wraps the lower-level React APIs. The server-side renderRequest function renders a component to an RSC Payload and sends it as the response. The client-side hydrate function hydrates the payload, and the fetchRSC function fetches a new payload for a client-side navigation .

// server.js
import express from 'express'
import { renderRequest } from '@parcel/rsc/node'
import { Page } from './Page'

const app = express()

app.get('/', async (req, res) => {
  await renderRequest(req, res, <Page />, { component: Page })
})

app.listen(3000)
// Page.jsx
'use server-entry'

export function Page() {
  return <h1>Hello</h1>
}

The "use server" directive is supported at the top of a file, not inline within a function. A file with "use server" exports async functions that the client can call. The setServerCallback function in @parcel/rsc/client defines the HTTP request that is made when a Server Function is called, and the server handles the POST request and calls the original function .


Complete Example Session

# ============================================
# PART 1: THE TEMPLATE
# ============================================
npm create parcel react-server my-rsc-app
cd my-rsc-app
// ============================================
// PART 2: THE TARGETS
// ============================================
{
  "targets": {
    "client": { "source": "index.html" },
    "server": { "source": "src/server.js" }
  }
}
// ============================================
// PART 3: THE SERVER COMPONENT
// ============================================
// src/Page.jsx
'use server-entry'

export function Page() {
  return <h1>Hello, RSC</h1>
}
// ============================================
// PART 4: THE SERVER
// ============================================
// src/server.js
import express from 'express'
import { renderRequest } from '@parcel/rsc/node'
import { Page } from './Page'

const app = express()

app.get('/', async (req, res) => {
  await renderRequest(req, res, <Page />, { component: Page })
})

app.listen(3000)
// ============================================
// PART 5: THE CLIENT COMPONENT
// ============================================
// src/LikeButton.jsx
'use client'
import { useState } from 'react'

export function LikeButton() {
  const [liked, setLiked] = useState(false)
  return <button onClick={() => setLiked(!liked)}>{liked ? 'Liked' : 'Like'}</button>
}
// ============================================
// PART 6: THE SERVER COMPONENT IMPORTING CLIENT
// ============================================
// src/Page.jsx
'use server-entry'
import { LikeButton } from './LikeButton'

export function Page() {
  return (
    <div>
      <h1>Hello</h1>
      <LikeButton />
    </div>
  )
}
// ============================================
// PART 7: THE CLIENT FETCH
// ============================================
// src/client.js
import { hydrate, fetchRSC } from '@parcel/rsc/client'

let updateRoot = hydrate()

async function navigate(pathname) {
  let root = await fetchRSC(pathname)
  updateRoot(root)
}
// ============================================
// PART 8: THE SERVER FUNCTION
// ============================================
// src/actions.js
'use server'

export async function likeComment(id) {
  await db.comment.update({ where: { id }, data: { likes: { increment: 1 } } })
}
// ============================================
// PART 9: THE SERVER CALLBACK
// ============================================
// src/client.js
import { setServerCallback } from '@parcel/rsc/client'

setServerCallback(async (id, args) => {
  const res = await fetch('/api', {
    method: 'POST',
    headers: { 'X-Server-Action-Id': id },
    body: JSON.stringify(args),
  })
  return res.json()
})
# ============================================
# PART 10: THE STATIC MODE
# ============================================
# package.json
# "react-static": { "source": "pages/**/*.tsx" }
# parcel build

The ten parts covered the template, the targets, the server component, the server, the client component, the import, the client fetch, the Server Function, the server callback, and the static mode.


Quick Reference

Rendering modes

ModeEntry pointUse case
Client-integratedExisting HTML + server targetAdd RSC to an SPA
Server-renderedServer codeDynamic pages
Staticpages/**/*.tsx with react-staticMarketing, blog

The directives

DirectiveLocationPurpose
"use client"Top of fileMark a client component
"use server"Top of fileMark a Server Function file
"use server-entry"Top of fileMark a page entry point (Parcel-specific)

The @parcel/rsc library

FunctionContextPurpose
renderRequestServerRender to RSC Payload
hydrateClientHydrate the payload
fetchRSCClientFetch a new payload for navigation
setServerCallbackClientDefine the Server Function transport

The targets

TargetSourceOutput
clientHTML fileClient bundles
serverServer codeServer bundle
react-staticpages/**/*.tsxStatic HTML

Best Practices

โœ… Do This:

// Use "use server-entry" on the page component
'use server-entry'                                           // โœ…
// Use "use client" on the interactive component
'use client'                                                 // โœ…
// Use the @parcel/rsc helpers for rendering and hydration
import { renderRequest } from '@parcel/rsc/node'             // โœ…
// Use the react-static target for static pages
{ "react-static": { "source": "pages/**/*.tsx" } }           // โœ…

โŒ Don’t Do This:

// Use "use server" inline in a function
export function Page() { 'use server'; ... }                 // โŒ (top of file only)
// Expect a router or a file convention
// Parcel is not a framework                               // โŒ
// Import the client component without "use client"
import { LikeButton } from './LikeButton'                    // โŒ (if it uses hooks)

Common Pitfalls

PitfallWhy It HappensFix
Server component runs on the clientMissing "use server-entry"Add the directive
Client component does not hydrateMissing "use client"Add the directive
Server Function is not callableMissing setServerCallbackDefine the transport
Static page is not generatedWrong target nameUse react-static
Build fails on @parcel/rsc importPackage not installedInstall @parcel/rsc

Real-World Examples

1. The template

npm create parcel react-server my-rsc-app

2. The server entry

'use server-entry'
export function Page() { return <h1>Hello</h1> }

3. The client component

'use client'
export function Button() { const [n, setN] = useState(0); ... }

4. The server render

await renderRequest(req, res, <Page />, { component: Page })

5. The client hydrate

let updateRoot = hydrate()

6. The client fetch

let root = await fetchRSC(pathname)

7. The Server Function

'use server'
export async function like(id) { ... }

8. The server callback

setServerCallback(async (id, args) => { ... })

9. The static target

{ "react-static": { "source": "pages/**/*.tsx" } }

10. The decision

SPA integration โ†’ client target + server target
Dynamic pages โ†’ server-rendered
Static pages โ†’ react-static target

Visual

The three modes

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CLIENT-INTEGRATED                                           โ”‚
โ”‚    Existing SPA + new server target                          โ”‚
โ”‚    Client fetches RSC Payload for specific routes            โ”‚
โ”‚                                                              โ”‚
โ”‚  SERVER-RENDERED                                             โ”‚
โ”‚    Server is the entry point                                 โ”‚
โ”‚    RSC Payload rendered per request                          โ”‚
โ”‚                                                              โ”‚
โ”‚  STATIC                                                      โ”‚
โ”‚    react-static target                                       โ”‚
โ”‚    HTML generated at build time                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The directive flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Page.jsx                                                    โ”‚
โ”‚    "use server-entry"                                        โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Parcel creates a code-splitting boundary                    โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ”œโ”€โ”€ Server dependencies โ†’ server bundle               โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ””โ”€โ”€ "use client" imports โ†’ client bundle              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The server rendering

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Express server                                              โ”‚
โ”‚    app.get('/', async (req, res) => {                        โ”‚
โ”‚      await renderRequest(req, res, <Page />, { component: Page })โ”‚
โ”‚    })                                                        โ”‚
โ”‚                                                              โ”‚
โ”‚  Page is a Server Component.                                 โ”‚
โ”‚  renderRequest produces the RSC Payload.                     โ”‚
โ”‚  The client hydrates the payload.                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The static mode

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  pages/index.tsx                                             โ”‚
โ”‚  pages/about.tsx                                             โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  parcel build (react-static target)                          โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  dist/index.html                                             โ”‚
โ”‚  dist/about.html                                             โ”‚
โ”‚                                                              โ”‚
โ”‚  Served from a CDN, hydrated in the browser.                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
RSC support introducedParcel v2.14.0 (beta)
Parcel’s approachBundler primitives, not a framework
Templatereact-server
Entry directive"use server-entry"
Client directive"use client"
Server directive"use server" (top of file)
Runtime library@parcel/rsc
Rendering modesClient-integrated, server-rendered, static
Static targetreact-static
Server dev supportparcel src/server.js with hot reload

Key takeaways:

  • Parcel is not a framework. It provides the bundler primitives and the @parcel/rsc library, and leaves the server, routing, and application structure to the developer. This is a deliberate design choice that enables incremental adoption and integration into existing apps .
  • The react-server template scaffolds a TypeScript RSC project. It creates the client and server targets, the server entry point, and the "use server-entry" directive in the page component. It is the fastest way to see the setup in action .
  • The "use server-entry" directive is Parcel-specific. It marks a server component as the entry point of a page, creating a code-splitting boundary and enabling the shared dependency extraction between pages .
  • The three rendering modes cover different use cases. Client-integrated RSC adds server rendering to an existing SPA. Server-rendered RSC makes the server the entry point and renders per request. Static rendering pre-renders to HTML at build time for pages that do not contain personalized data .
  • The "use client" and "use server" directives are the standard React directives. The "use client" marks a client component, and the "use server" marks a file of Server Functions. Parcel supports "use server" at the top of a file, not inline within a function .
  • The @parcel/rsc library wraps the lower-level React APIs. renderRequest renders a component to an RSC Payload on the server. hydrate hydrates the payload on the client. fetchRSC fetches a new payload for a client-side navigation. setServerCallback defines the transport for Server Functions .

Remember: Parcel’s React Server Components support is a set of bundler primitives, not a framework. The react-server template is the starting point. The "use server-entry" directive marks the page entry point. The "use client" and "use server" directives are the standard React boundary markers. The @parcel/rsc library provides the rendering and hydration helpers. And the three modesโ€”client-integrated, server-rendered, and staticโ€”allow the same app to mix pages that need different rendering strategies.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!