Blog

Part 33 · Server Components and Server Functions, Explained

Some React components run only on a server and send just their result. Learn Server Components, ‘use client’, props that can cross, Server Functions and how to keep them safe.

Every example so far ran in your browser. Even the project from Part 10 only used a server to send files. All of React’s work happened in the browser.

Some React apps split the work. Part of the app runs on a server, the computer that sends the page. The rest runs in the browser. Part 13 said HTML built on a server can only show “Loading…”. Part 29 named 'use server'. This part explains both.

These features need a server and a framework, a bigger tool built on top of React. The playground can’t run them, so only one example here has a Run button. Instead, we built a small notes app with Next.js 16.4.0 and measured it.

Try this first

Here are two files from the notes app. Read them, but don’t look below yet.

// app/page.tsx
import { readFile } from 'node:fs/promises'
import { LikeButton } from './like-button'
import { Hideable } from './hideable'
import { NoteForm } from './note-form'

type Note = { id: number; text: string }

export default async function Page() {
  console.log('Page runs')
  const json = await readFile('notes.json', 'utf8')
  const notes: Note[] = JSON.parse(json)

  return (
    <main>
      <h1>My notes</h1>
      <Hideable>
        <ul>
          {notes.map(note => (
            <li key={note.id}>
              {note.text} <LikeButton />
            </li>
          ))}
        </ul>
      </Hideable>
      <NoteForm />
    </main>
  )
}
// app/like-button.tsx
'use client'

import { useState } from 'react'

export function LikeButton() {
  const [likes, setLikes] = useState(0)
  console.log('LikeButton runs')
  return <button onClick={() => setLikes(likes + 1)}>Likes: {likes}</button>
}

readFile comes from Node.js. It reads a file from the disk of the computer it runs on. notes.json holds two notes: “Buy milk” and “Call Ana”. Hideable and NoteForm come later in this part.

Now make three guesses.

  1. Where does Page runs appear: in the browser’s Console, in the server’s terminal, or in both?
  2. Where does LikeButton runs appear?
  3. Does the browser download the code that reads notes.json?

Here is what happened. We built the app for production, so there was no Strict Mode and no double logs. We added one Next.js setting, so the server renders the page for every visit. Then we loaded the page once in a real browser.

  • Page runs appeared once, in the server’s terminal. The browser’s Console never showed it.
  • LikeButton runs appeared in both places. The server printed it 2 times, once for each note. The browser printed it 2 times too. A click on a Likes button printed it once more.
  • The browser loaded 7 JavaScript files for the page. None of them had readFile, notes.json or Page runs in it. One of them had LikeButton runs.

Two places React can run

LikeButton ran in both places because of an idea older than Server Components. So far, React built every page in the browser. A page can also be built on a server first. React renders the components there and turns the result into HTML text. This is called server-side rendering, or SSR. The browser shows that HTML at once, before any JavaScript has loaded.

We tried this with React’s own renderToString, on a small app with a heading and one LikeButton. It gave back this HTML:

<main><h1>My notes</h1><p>Buy milk <button>Likes: <!-- -->0</button></p></main>

The <!-- --> is a comment React put between two pieces of text. A browser doesn’t show it.

This HTML is only a picture of the page. We put it into a page and clicked the button twice. It still said “Likes: 0”.

Hydration

To make the HTML work, React renders the same components again in the browser, and connects them to the HTML. This is called hydration. In the words of React’s docs, hydration “turns the initial HTML snapshot from the server into a fully interactive app”.

We called React’s hydrateRoot on that page, without <StrictMode>. LikeButton ran once. React kept the same <button> that the HTML made, and didn’t build a new one. After one click, it said “Likes: 1”.

Hydration has one rule: hydrateRoot “expects the rendered content to be identical with the server-rendered content”. Part 27 met this rule with a store. You rarely call hydrateRoot yourself: “If you use a framework, it might do this call for you.”

Server Components: they stay on the server

A Server Component runs only on the server, and its code is never sent to the browser. Page is one. In a framework that supports them, a component is server code by default. It becomes client code only if its file starts with 'use client', or if client code imports it. React’s docs say an app like this “is server-rendered by default”.

React’s docs describe two times when Server Components can run: “once at build time” or “for each request using a web server”. A request is one visit asking the server for a page. We saw both.

  • As written, Next.js ran Page once, when we built the app. The build printed Page runs. Then 3 visits to the page printed nothing.
  • With the Next.js setting that renders the page for each request, 3 visits printed Page runs 3 times.

What a Server Component can do

Read data directly. Page reads a file from the server’s disk. It could also talk to a database. You don’t need to build a separate address for the browser to fetch the data from.

Wait for data while it renders. Page is an async function. It uses await before it returns its JSX. React’s docs say “Async Components are a new feature of Server Components”. So the first HTML already holds the notes. Compare Part 13, where the first render could only show “Loading…”.

What a Server Component can’t do

A Server Component runs on the server and then it is gone. React’s docs say Server Components don’t stay in memory after they render, and “cannot have their own state”. So a Server Component can’t use:

  • useState or useReducer, because there is no state to remember;
  • useEffect, because Effects run in the browser after a commit;
  • event handlers like onClick. React’s docs say they “can only be defined in Client Components”.

React’s docs also say Server Components “cannot use most Hooks”. Their list of what must run in the browser names every Hook but two: use and useId.

What the server sends

The result of the Server Components isn’t HTML. It is React’s own text format. Next.js calls it the RSC Payload. RSC is short for React Server Components. We found this line for our page inside the HTML the server made:

6:["$","main",null,{"children":[["$","h1",null,{"children":"My notes"}],["$","$L12",null,{"children":["$","ul",null,{"children":[["$","li","1",{"children":["Buy milk"," ",["$","$L13",null,{}]]}],["$","li","2",{"children":["Call Ana"," ",["$","$L13",null,{}]]}]]}]}],["$","$L14",null,{}]]}]

The main, h1, ul and li tags are there, already filled with the notes. Page itself is not there: this is its output. In the places of the Client Components stand $L12, $L13 and $L14. Each points to another line, like 13:I[91375,["$2","$11"],"LikeButton"], which says where to find the code for LikeButton.

Step through the whole first visit:

1. The tree of one page. Page is a Server Component. Dashed boxes are Client Components. 2. On the server, React runs Page. Page reads notes.json from the server's disk. 3. Client Components are not part of this result. In their place goes a reference to their code. 4. To make the first HTML, the server also runs the Client Components once. 5. It sends HTML, the rendered result, and JavaScript: Client Components, React, Next.js. 6. The browser shows the HTML at once. The buttons are there, but they don't work yet. 7. React hydrates: it runs the Client Components and attaches them to the HTML. 8. You click Likes. Only the browser runs. Nothing is sent to the server. the server the browser Pageserver <h1>My notes</h1>server Hideableclient <li>Buy milk</li> …server LikeButton ×2client NoteFormclient dashed = Client Component ('use client') HTMLrendered resultJavaScript My notes Hide Likes: 0 Likes: 0 Add Buy milk Call Ana

One page, split between the server and the browser. Press play, or step through it.

The same steps, in words:

  1. The tree has one Server Component, Page, and three Client Components.
  2. On the server, React runs Page, which reads notes.json.
  3. In the result, each Client Component is only a reference to its code.
  4. To make the first HTML, the server also runs the Client Components once.
  5. The server sends the HTML and the RSC Payload. It also sends JavaScript: the Client Components’ code, plus React and the framework’s own code. Page‘s code stays behind.
  6. The browser shows the HTML at once. The buttons don’t work yet. With JavaScript turned off, clicking Likes left it at 0.
  7. React hydrates the Client Components. Our browser printed LikeButton runs 2 times here.
  8. You click Likes. It shows “Likes: 1”. The click sent 0 requests to the server.

An everyday example

Think of a meal sent to your home. Most of it arrives cooked. You get the food, not the kitchen or the recipe. That is a Server Component: only its result travels. One dish is a salad, with its dressing in a small bottle. You pour it at home, whenever you like. That is a Client Component: its code travels with it, so it can still change in your hands.

The exact version

The meal story breaks in two places. First, the kitchen can cook once, early, or for each order: build time or each request. Second, a Client Component doesn’t run only “at home”. The server also runs it once, to make the first HTML. Hydration then makes that HTML work in the browser.

Client Components and 'use client'

LikeButton needs state and a click handler, so it must be a Client Component. The first line of its file says so:

'use client'

This line is a directive: a short string that gives tools an instruction. Part 22 met one at the top of a function. This one goes “at the very beginning of a file, above any imports or other code”, in React’s words. Comments above it are OK, so our files start with their name.

'use client' marks the file and everything it imports as client code. That means the files it imports, the files those import, and so on. So:

  • You don’t need 'use client' in every file. Put it where browser code starts.
  • A big library imported there is downloaded too. So add 'use client' to “the files that define your interactive components”, as Next.js says.
  • A Server Component imported by client code becomes client code. So a Client Component can’t import a Server Component.

A Client Component also runs on the server

React’s docs define Client Components as “components in a render tree that are rendered on the client”. But the server also runs them once, to make the first HTML. So “client” means “its code is sent to the browser, and it can be interactive there”. It doesn’t mean “it never runs on a server”. Code that only works in a browser, like localStorage, still belongs in an Effect or an event handler.

There is no 'use server' for components

Server Components have no directive. React’s docs warn that “there is no directive for Server Components”. 'use server' means something else: a Server Function. That comes after one more rule, about props.

Props that cross from server to browser

Page gives props to Client Components. Those props travel over the network, inside the RSC Payload. So they must be serializable. A serializable value can be turned into text, and then back into the same kind of value.

React’s docs list what can cross from a Server Component to a Client Component:

Can cross Can’t cross
strings, numbers, bigint, true and false, undefined, null other functions
symbols made with Symbol.for('...') symbols made with Symbol('...')
arrays, Map, Set, typed arrays, ArrayBuffer class instances, other than the built-ins on the left
Date objects with a null prototype
plain objects, like { amount: 5 }, holding values from this list
JSX, including Server and Client Components
promises
Server Functions
functions exported from a 'use client' file, such as a Client Component

A class instance is an object made with new from a class, like new Price(5).

We tried a few. A plain object, { amount: 5 }, worked. A Date arrived in the Client Component as a real Date. A function stopped the page with an error. So did new Price(5). You’ll see both errors under Common mistakes.

Server Components inside Client Components

Hideable is a Client Component, and the list of notes sits inside it. Yet the notes didn’t become client code. Here is Hideable:

// app/hideable.tsx
'use client'

import { useState, type ReactNode } from 'react'

export function Hideable({ children }: { children: ReactNode }) {
  const [open, setOpen] = useState(true)
  return (
    <section>
      <button onClick={() => setOpen(!open)}>{open ? 'Hide' : 'Show'}</button>
      {open && children}
    </section>
  )
}

Hideable never imports the list. Page wrote the <ul> and passed it in as children. So Page made those elements on the server, and Hideable only decides where they go.

You can see it in the RSC Payload above. $L12 is Hideable, and its children is the finished <ul>, with the text already in it. In our browser, clicking Hide removed both notes, and Show brought them back.

This is the same idea as Part 19. The component that writes the JSX makes the element, and a wrapper only places it. Here, that also decides where the code runs. React’s docs put it this way: “a parent-child render relationship between components does not guarantee the same render environment.”

Server Functions: 'use server'

A Server Function is a function the browser can call, but which runs on the server. React’s docs say Server Functions “allow Client Components to call async functions executed on the server”. Here is ours:

// app/actions.ts
'use server'

import { readFile, writeFile } from 'node:fs/promises'
import { refresh } from 'next/cache'

type Note = { id: number; text: string }

export async function addNote(prevState: string, formData: FormData) {
  const text = formData.get('text')
  console.log('addNote runs, text:', JSON.stringify(text))
  if (typeof text !== 'string' || text.trim() === '' || text.length > 100) {
    return 'Please write a note of 1 to 100 letters.'
  }
  const notes: Note[] = JSON.parse(await readFile('notes.json', 'utf8'))
  notes.push({ id: notes.length + 1, text })
  await writeFile('notes.json', JSON.stringify(notes))
  refresh()
  return 'Saved.'
}

There are two places to put 'use server':

  • At the top of a file, as here. Every exported function in it becomes a Server Function. Client code can import Server Functions only from a file like this.
  • As the first line inside one async function, in a server file such as a Server Component. It can then pass the function to a Client Component as a prop.

Either way, the function must be async, because calling it always goes over the network.

refresh is not React. It is how Next.js says “this page’s data changed, so render it again”.

The form is a Client Component. It uses useActionState from Part 29, with addNote as its action:

// app/note-form.tsx
'use client'

import { useActionState } from 'react'
import { addNote } from './actions'

export function NoteForm() {
  const [message, formAction, isPending] = useActionState(addNote, '')
  return (
    <form action={formAction}>
      <input name="text" aria-label="New note" maxLength={100} />
      <button disabled={isPending}>Add</button>
      <p>{message}</p>
    </form>
  )
}

This file imports addNote, but the browser gets only “a reference to the Server Function”, in React’s words. In the browser’s JavaScript, we found the name addNote next to a long id. The text addNote runs and the writeFile call were not there.

What one call looks like

We typed “Buy bread” and clicked Add. Step through what happened:

1. You type “Buy bread” and click Add. The form's action is the Server Function addNote. 2. The browser sends one POST request: the function's id and the form's fields. 3. On the server, addNote runs. It checks the text, then saves it in notes.json. 4. The page's data changed, so the server runs Page again. 5. The answer comes back: the message “Saved.” and the new rendered result. 6. React updates the page: three notes, the message Saved. and an empty box. the browser the server My notesBuy milkCall AnaBuy breadBuy breadAddSaved. addNote(prevState, formData)'use server' Page notes.jsonBuy milk, Call Ana POST: id of addNotetext = Buy bread "Saved." + new resultlist with Buy bread

A Server Function call, from the click to the new page. Press play, or step through it.

  1. You type “Buy bread” and click Add. The form’s action is addNote.
  2. The browser sends one POST request, a request that sends data. A request carries short labelled notes called headers. Next.js put the id of addNote in a header called next-action. The form’s fields came too, with text set to Buy bread.
  3. On the server, addNote runs. The terminal printed addNote runs, text: "Buy bread". The text passes the check, so it is saved in notes.json.
  4. refresh() asked for a new render, so the terminal printed Page runs.
  5. The answer came back in React’s text format. It held "Saved." and the new list, with “Buy bread” in it.
  6. React updated the page: three notes, the message “Saved.” and an empty box.

The name next-action comes from Next.js, not React. The id was 42 letters and numbers long, and it changed each time we built the app.

Forms that work without JavaScript

Part 29 said a form can work “without JavaScript enabled” when its action is a Server Function. We tried it. We turned JavaScript off in the browser, typed “Water plants” and clicked Add. The browser sent it as a plain HTML form and loaded a new page, with four notes and “Saved.”. The Likes buttons did nothing. If the form is sent while JavaScript is still loading, Next.js keeps it and sends it after hydration.

Not for reading data

Server Functions are made for changes, like saving or deleting. React’s docs say they are “not recommended for data fetching”. Read data in a Server Component, as Page does.

You’ll also see the name Server Actions. React’s docs used it for all Server Functions until September 2024. Now the name means a Server Function used as an action. One example is a function passed to a form’s action, like addNote. Next.js uses both names.

Server Functions are open to anyone

A Server Function looks like a normal function. But it is really an endpoint: a place on a server that a program can send requests to. The docs of Next.js say anyone can reach one with a direct POST request. The id is not a secret: we found it in the page’s HTML and JavaScript.

We tested it. The input box has maxLength={100}, so the browser lets you type 100 letters at most. We skipped the browser. A short Node.js program sent the same request, but with a note of 500 letters. addNote ran, and the terminal printed all 500 letters. The check inside addNote refused the note, and notes.json didn’t change.

So maxLength didn’t protect the server. The check on the server did. React’s docs say the arguments “are fully client-controlled”. So:

  • Check every value. React’s docs say to treat the arguments as input you can’t trust. Check each value’s type, length and range, on the server.
  • Check who is asking. React’s docs also say to check that the signed-in user is allowed to do that action. That often means this one record: is this note theirs?

Here is a Server Function that deletes a note, with both checks. getCurrentUser, findNote and deleteFromDatabase stand for your app’s own code. declare tells TypeScript they exist somewhere else.

'use server'

type User = { id: number }
type Note = { id: number; ownerId: number; text: string }
declare function getCurrentUser(): Promise<User | null>
declare function findNote(id: number): Promise<Note | null>
declare function deleteFromDatabase(id: number): Promise<void>

export async function deleteNote(formData: FormData) {
  const user = await getCurrentUser()
  if (!user) {
    throw new Error('Not signed in')
  }
  const id = Number(formData.get('id'))
  if (!Number.isInteger(id) || id < 1) {
    throw new Error('Not a note id')
  }
  const note = await findNote(id)
  if (!note || note.ownerId !== user.id) {
    throw new Error('Not allowed')
  }
  await deleteFromDatabase(id)
}

The last check matters most. Without it, a signed-in user could delete anyone’s note by sending another id. The docs of Next.js show the same check.

These checks throw, because only a request that skipped your page can fail them. For errors a normal user can fix, return a message instead, as addNote does. Part 29 explained that choice.

Where you can use all this

Not in this series’ playground, which runs React only in the browser. Not in a new Vite project like Part 10’s, as it is. Its main.tsx calls createRoot, so it is client-only. Vite can do SSR with extra setup. It also has an experimental add-on for Server Components, @vitejs/plugin-rsc, which React Router uses.

You need a framework or a build tool that supports them. React’s docs say building it yourself “currently requires deep expertise”. They call the Next.js App Router “the most complete implementation” today. React Router’s docs say its support “is experimental and subject to breaking changes”.

React’s docs add a warning for frameworks. The parts they use to build this “may break between minors in React 19.x”. So frameworks should use one exact React version, or a Canary version. That is a test version of React that comes out before the normal one. Next.js 16.4.0 brings its own copy of React, 19.3.0-canary-278794d7-20261002.

You don’t have to move every app to a server. A client-only app like Part 10’s is still a normal React app.

Common mistakes

We made each of these mistakes in the notes app, running the development server, next dev. Some messages come from React’s own server code, which Next.js includes. Others come from Next.js’s compiler, the tool that turns your files into what the server and the browser run.

Using useState in a Server Component

import { useState } from 'react'

function Page() {
  const [likes, setLikes] = useState(0)
  return <p>Likes: {likes}</p>
}

export default Page

There is no 'use client' here, so in a framework this is a Server Component. Next.js’s compiler stopped with this error:

Error: You're importing a module that depends on `useState` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.

The fix: move the part with state into its own file that starts with 'use client', as LikeButton is. Keep the page itself on the server.

An event handler in a Server Component

<button onClick={() => console.log('hi')}> in a Server Component gave this error, from React:

Error: Event handlers cannot be passed to Client Component props.

The handler is a function, and a function can’t travel to the browser. The fix is the same: put the button in a Client Component.

Passing a function across the line

We passed makeText={() => 'Hello'} from a Server Component to a Client Component. React stopped the page:

Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". Or maybe you meant to call this function rather than return it.

The fix depends on the job. To pass a value, call the function on the server and pass its result. To run code in the browser, write that function inside the Client Component. To run code on the server when the user acts, make it a Server Function.

Passing a class instance across the line

We passed price={new Price(5)}, where Price is a class with an amount field. React stopped the page:

Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.

The fix: pass a plain object, price={{ amount: 5 }}. That worked.

Putting 'use client' everywhere

Putting 'use client' on every file can look like a quick fix for the errors above. But then all that code goes to the browser, and the page can’t read the disk any more. React’s docs say reading the file system “is possible only in a Server Component”.

An async component stops working too. This one is plain browser code, so you can run it here:

async function Notes() {
  console.log('Notes runs')
  const text = await new Promise<string>(r => setTimeout(() => r('Buy milk'), 100))
  return <p>{text}</p>
}

export default function App() {
  return <main><h1>My notes</h1><Notes /></main>
}

Press Run. The page stays empty, without even “My notes”. The Console shows this error:

<Notes> is an async Client Component. Only Server Components can be async at the moment. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

The Console also shows Notes runs again and again. In our test, with Strict Mode on as in the playground, it ran 8 times in the first 300 ms. It was still running at 2 seconds. To wait for data in the browser, use use() with <Suspense>, and make the promise outside the render. Part 31 showed how.

The fix: add 'use client' only to the small, interactive files.

A Server Function that isn’t async

'use server'

export function save() {
  console.log('saved')
}

Next.js’s compiler refused to compile it: Error: Server Actions must be async functions. Write export async function save().

Practice

These are thinking tasks. The playground can’t run a server, so write your answer down first, then open the answers.

  1. Which of these must be Client Components? (a) An article read from a database. (b) A search box that filters a list while you type. (c) A footer that shows the year. (d) A button that saves the theme in localStorage.
  2. A Server Component renders <Card info={info} />, and Card starts with 'use client'. Which of these values for info work? (a) { title: 'Hi', tags: ['a', 'b'] } (b) new Date() (c) { title: 'Hi', onOpen: () => {} } (d) new Map([['a', 1]])
  3. This Server Function lets a user change the name of a note. What is missing? export async function rename(formData: FormData) { await saveName(Number(formData.get('id')), String(formData.get('name'))) }
Answers
  1. (b) and (d). The search box needs state and a typing handler. The theme button needs a click handler and localStorage, which only the browser has. The article and the footer only show things, so they can stay Server Components.
  2. (a), (b) and (d) work. A plain object with strings and an array is on React’s list. So are Date and Map. (c) fails, because onOpen is a function that isn’t a Server Function.
  3. All the checks. It doesn’t check that a user is signed in. It doesn’t load the note and check that it belongs to that user. And it doesn’t check the values: id could be any number, or not a number at all, and name could be empty or very long. Anyone can call it with anything.

Interview questions

Try to answer each one out loud before you open the answer.

What is a Server Component?

A component that runs only on the server, at build time or for each request. Only its result is sent to the browser, never its code. It can be async, and it can read files or a database directly. It can’t have state, Effects or event handlers.

A strong answer adds that this needs a framework, like the Next.js App Router. In such a framework, a component is server code unless its file has 'use client' or client code imports it.

How are Server Components different from server-side rendering (SSR)?

SSR runs components on the server to make the first HTML. Then the browser runs the same components again to hydrate, so their code still goes to the browser. A Server Component’s code never does.

The two work together. In our app, SSR still ran the Client Component LikeButton on the server, to make the first HTML.

What does 'use client' do? Does a Client Component run only in the browser?

It marks a file, and everything that file imports, as client code. That code is sent to the browser. It must come before any imports or other code in the file. It draws a line between server code and client code. So you put it where client code starts, not in every file.

A Client Component doesn’t run only in the browser. With SSR, the server runs it once to make the first HTML. So browser-only code like window or localStorage still belongs in Effects or event handlers.

What can a Server Component pass as props to a Client Component?

Only serializable values, because props travel over the network as text. That means simple values like strings and numbers, plus arrays, Map, Set, Date and plain objects of them. JSX, promises, Server Functions and Client Components can cross too. Other functions and instances of your own classes can’t.

A strong answer gives the fixes. Pass a function’s result, turn a class instance into a plain object, or use a Server Function.

What is a Server Function, and how does the browser call it?

An async function marked with 'use server'. The browser gets a reference to it, not its code. When client code calls it, React sends a request with the arguments. The function runs on the server, and its return value comes back. In our app, that was one POST request. It is for changes, not for fetching data.

A strong answer adds three things from React’s docs. Call it in a Transition, which a form’s action does for you. Its arguments and its return value must be serializable. And frameworks “typically process one action at a time”.

Why are Server Functions a security risk, and what do you do about it?

Each one is a public endpoint. Anyone can send it a request with any values, without using your page. Checks in the browser, like maxLength, don’t count. So inside every Server Function, check the values: their type, length and range. Also check that the signed-in user may do this action on this record, for example that they own the note.

A strong answer also says that the return value goes to the browser, so it must not hold secrets. React has experimental functions that stop chosen values from reaching client code. Next.js also locks the values a Server Function takes from its component. But its docs say not to rely on that alone.

Sources

  • Server Components, react.dev: build time or each request, “Async Components are a new feature of Server Components”, “there is no directive for Server Components”, composing with Client Components, async components not being supported on the client, and frameworks pinning a React version or using Canary (“may break between minors in React 19.x”).
  • 'use client', react.dev: where the directive goes, the module and what it imports, “server-rendered by default”, the definitions of Server and Client Components, “does not guarantee the same render environment”, the list of serializable props, the limits of Server Components (no state after render, “most Hooks”, event handlers, use and useId) and reading the file system “possible only in a Server Component”.
  • 'use server', react.dev: the directive, file level or function level, importing from client code only from a marked file, async only, Transitions, one action at a time, security (“fully client-controlled”, untrusted input, checking the logged-in user, the taint APIs), “exposed server endpoints”, serializable arguments, forms, and “not recommended for data fetching”.
  • Server Functions, react.dev: what they are, when a Server Function is a Server Action, the name before September 2024, and the reference the framework makes.
  • hydrateRoot and renderToString, react.dev: what hydration does, that the content must match, and that a framework calls it for you.
  • Creating a React App and Build a React App from Scratch, react.dev: start with a framework, the Next.js App Router as “the most complete implementation”, and “requires deep expertise”.
  • Server-Side Rendering, vite.dev: SSR with Vite needs its own setup.
  • React v19, React blog: Server Components and Server Actions in React 19.
  • Next.js 16.4.0 docs (shipped inside the next package): Server and Client Components (the RSC Payload, prerendering HTML from Client Components, where to put 'use client'), Mutating Data (direct POST requests, refresh, queued submissions before JavaScript loads) and Data Security (Server Actions reachable by direct POST, IDs that change between builds, checking that the user owns the record, encrypted closed-over values and “We don’t recommend relying on encryption alone”).
  • React Server Components, React Router docs: support “is experimental”, and the experimental @vitejs/plugin-rsc it uses.
  • The HTML, the hydration test and the async component’s error come from running React 19.3.0 for this post. Everything about the notes app comes from a real Next.js 16.4.0 app, made with create-next-app 16.4.0 and checked in headless Chromium. Its App Router runs its own React build, 19.3.0-canary-278794d7-20261002.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.