Irbisa · Cheatsheet

Server Components & Next.js

React Server Components, server functions, CSR vs SSR vs SSG vs ISR, streaming, hydration errors, the Next.js App Router, caching

Senior Developer · 22 questions

  1. 01

    What are React Server Components, and how do they differ from Client Components?

    Server Components render only on the server (at build time or per request), can be async and read a database or files directly, and reach the browser as serialized UI with no JavaScript.

    // app/posts/[id]/page.tsx: a Server Componentexport default async function PostPage({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const post = await db.post.findUnique({ where: { id } });  if (!post) notFound();…
  2. 02

    What is the difference between CSR, SSR, SSG and ISR, and when would you choose each?

    They differ in where and when the HTML is produced: CSR builds it in the browser from JavaScript, SSR on the server for each request, SSG once at build time, and ISR at build time with pages regenerated in the background after a set time or on demand.

  3. 03

    How do the App Router file conventions work: page, layout, loading, error and not-found?

    In the App Router each folder under app/ is a URL segment, and special files in it define that segment's UI: page.tsx makes it routable, layout.tsx wraps it and persists across navigation, and loading.tsx and error.tsx add a Suspense boundary and an error boundary around it.

    // app/dashboard/error.tsx'use client'; export default function DashboardError({  error,  reset,…
  4. 04

    What does "use client" actually mark, and where should you put the boundary?

    "use client" at the top of a file marks a boundary in the module graph: that module and everything it imports become client code, bundled and hydrated in the browser.

    // app/products/page.tsx: Server Componentexport default async function ProductsPage() {  const products = await getProducts();  return (    <FilterPanel>      {/* rendered on the server, passed through the client boundary */}…
  5. 05

    What can a Server Component pass as props to a Client Component, and what fails?

    Props that cross the boundary must be serializable by React: primitives, plain objects and arrays, Date, Map, Set, typed arrays, promises, JSX and server functions.

    // app/posts/[id]/page.tsx: Server Componentexport default async function PostPage({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const commentsPromise = getComments(id); // not awaited: the page streams on  return (    <Suspense fallback={<p>Loading comments...</p>}>…
  6. 06

    Find the bug: this page fails with "Event handlers cannot be passed to Client Component props."

    // app/cart/page.tsxexport default async function CartPage() {  const cart = await getCart();   return (    <div>      <p>{cart.items.length} items</p>      <button onClick={() => console.log('checkout', cart.id)}>        Checkout      </button>    </div>  );}

    CartPage is a Server Component, and a Server Component cannot attach event handlers: a function cannot be serialized into the RSC payload, and none of this component's code runs in the browser to call it.

  7. 07

    How do you keep secrets and server-only code out of the client bundle in a Server Components app?

    Code imported only by Server Components never ships, but one stray import from a Client Component pulls it into the bundle.

    // lib/data/users.tsimport 'server-only'; export async function getProfile(userId: string) {  const user = await db.user.findUnique({ where: { id: userId } });  if (!user) return null;…
  8. 08

    What are Server Functions (Server Actions), and how do you call one from a form?

    A Server Function is an async function marked with "use server" that runs on the server but can be called from the client: React sends the arguments in a POST request and returns the result.

    // app/todos/actions.ts'use server'; export async function createTodo(  _prev: { error?: string },  formData: FormData,…
  9. 09

    What are Route Handlers (route.ts) in the App Router, and when do you use one instead of a Server Action?

    A route.ts file exports functions named after HTTP methods (GET, POST, DELETE) that take a Request and return a Response: a plain HTTP endpoint at that URL.

    // app/api/webhooks/payments/route.ts: called by the payment provider, not by your UIexport async function POST(request: Request) {  const body = await request.text(); // the raw body: the signature is computed over it  const signature = request.headers.get('x-signature') ?? '';  if (!(await verifySignature(body, signature, process.env.PAYMENTS_WEBHOOK_SECRET!))) {    return new Response('Invalid signature', { status: 400 });…
  10. 10

    What does proxy.ts (formerly middleware.ts) do in Next.js 16, and what should you not put in it?

    proxy.ts in the project root exports one function that runs before a request reaches its route and can redirect, rewrite, set headers and cookies, or respond itself.

    // proxy.ts (Next.js 16; formerly middleware.ts with `export function middleware`)import { NextResponse, type NextRequest } from 'next/server'; export function proxy(request: NextRequest) {  // optimistic check: no cookie means no session, so do not even render the page  if (request.nextUrl.pathname.startsWith('/account') && !request.cookies.has('session')) {…
  11. 11

    How do you fetch data in Server Components, and how do you avoid request waterfalls?

    Make the component async and await the data directly (a database call, an ORM or fetch), with no useEffect and no client API layer.

    export default async function DashboardPage() {  // both requests start before either is awaited  const [user, orders] = await Promise.all([getUser(), getOrders()]);   return (    <>…
  12. 12

    How does streaming SSR work with Suspense and loading.tsx?

    With streaming the server sends HTML in chunks over one response: first the shell with Suspense fallbacks, then each suspended part as it resolves, as HTML plus a small inline script that swaps it into place.

    // app/product/[id]/page.tsxexport default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const product = await getProduct(id); // fast: part of the first chunk   return (…
  13. 13

    What causes a hydration mismatch error, and how do you fix one?

    Hydration attaches React to server-rendered HTML and expects the first client render to produce the same markup.

  14. 14

    Find the bug: the theme button shows a hydration mismatch error, but only for users who chose the dark theme.

    // ThemeToggle.tsx'use client'; export function ThemeToggle() {  const saved = typeof window !== 'undefined' ? localStorage.getItem('theme') : null;  const [theme, setTheme] = useState(saved ?? 'light');   return (    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>      {theme === 'dark' ? 'Light mode' : 'Dark mode'}    </button>  );}

    The first render reads localStorage only in the browser, so the server renders "Dark mode" while a dark-theme user's browser renders "Light mode" during hydration, and the text differs.

  15. 15

    When would you choose a client-side SPA (Vite and React Router) over Next.js with Server Components?

    Choose a SPA when SEO and first-load speed for anonymous visitors do not matter and the app is highly interactive behind a login: an admin panel, an internal tool, an editor.

  16. 16

    How do you handle SEO in the Next.js App Router: metadata, generateMetadata, sitemap.ts and canonical URLs?

    Export a static metadata object, or an async generateMetadata when the tags depend on params or data, from a page or layout; Next.js renders them as <title>, <meta> and <link> tags in the server HTML.

    // app/layout.tsxexport const metadata: Metadata = {  metadataBase: new URL('https://shop.example'),  title: { template: '%s | Shop', default: 'Shop' },};…
  17. 17

    Why is every Server Action a public endpoint, and how do you secure one?

    React turns each server function into an HTTP endpoint identified by an id the client sends in a POST, and anyone can call it with any arguments, not only your form.

    // app/posts/actions.ts'use server'; const RenameInput = z.object({ postId: z.string().min(1), title: z.string().min(1).max(200) }); export async function renamePost(formData: FormData) {…
  18. 18

    How does caching work in the Next.js App Router, and what changed in Next.js 15 and 16?

    Next.js caches at several layers: request memoization within one render, the Data Cache for results across requests, the Full Route Cache for prerendered pages and the client Router Cache.

    import { cacheLife, cacheTag } from 'next/cache'; export async function getProducts(category: string) {  'use cache';  cacheLife('hours');  cacheTag('products', `products-${category}`);…
  19. 19

    After a mutation, how do you make the App Router show fresh data: revalidatePath, revalidateTag, updateTag or router.refresh?

    Invalidate the cache entries the write made stale, from the server function that did the write: revalidateTag for every cached read tagged with that data, revalidatePath for one route.

    'use server'; import { revalidateTag, updateTag } from 'next/cache'; export async function publishPost(postId: string) {  await requireEditor();…
  20. 20

    What does React's cache() do in Server Components, and how is it different from Next.js "use cache"?

    cache(fn) from React memoizes a function for one server request: components that call getUser(id) with the same arguments share one call and one result.

    import { cache } from 'react'; export const getUser = cache(async (id: string) => {  return db.user.findUnique({ where: { id } });});…
  21. 21

    Find the bug: after deploying, users sometimes see another user's name in the page header.

    // lib/current-user.tslet currentUser: User | null = null; // the layout, the header and the page all call it: query the database only onceexport async function getCurrentUser() {  if (!currentUser) {    const session = await auth();    currentUser = await db.user.findUnique({ where: { id: session.userId } });  }  return currentUser;} // app/Header.tsxexport async function Header() {  const user = await getCurrentUser();  return <p>Signed in as {user?.name}</p>;}

    currentUser lives in module scope, and a server module is loaded once per process and shared by every request: the first visitor after a start is cached, and everyone after them on that instance gets their name.

  22. 22

    What does the server send for Server Components (the RSC payload), and how does the client use it on navigation?

    The RSC payload (the "Flight" format) is a streamed serialization of the rendered tree: elements, text, Client Component props and references to their JavaScript chunks instead of their code.