React Server Components in Next.js 13

Abhishek Yadav

@h6165

FAQ

Isn't it already there in Next.js ? The SSR thing ?

SSR vs Server Components

SSR - we render the component on server side, usually with some data. The component is then *sent* to the browser where it becomes interactive (hydration)

 

Server Component - component rendered on server. Output (not component) sent to browser

What's the use ?

Server Component

  • Reduced bundle size: can use heavy libraries that don't need to be sent to the client 
  • Can perform operations that can't be done on client - like database access

What's the catch

Server Component - can't be interactive - can't useState, can't use the context API


//
// Next.js: components in the 'app' directory
//

import { heavyFunction } from 'heavy-library';

export function MyComponent() {
  const result = heavyFunction()
  return (
    <div>
      { result }
    </div>
  );
}

Example: server component in Next.js

No code from 'heavy-library' reaches the browser

'use client'; // Next.js: declare it's a client component

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Example: client components in Next.js

  • Client components - should declare themselves as so: 'use client'
  • Other than that there's nothing special
// Next.js
// client component called within server component
//

import { heavyFunction } from 'heavy-library';
import { Counter as CounterOnClient } from './counter'

export function MyServerComponent() {
  const result = heavyFunction()
  return (
    <div>
      { result }
      <CounterOnClient />
    </div>
  );
}

Example

Call Client component from the server component

  • Server components can call client components*
  • Client components can also call server components*

 

*conditions apply

Mingling Server and client components

Server Components

  • Dan Abramov announced in Dec 2020
  • Shared a long video, and some sample code
  • The idea being: build it for frameworks - like Next.js
  • Next.js 13 incorporated it, and released in Oct 2022
  • Feature is still in beta 

The project status

Server components

  • Props: server component -> client
  • Import: server component in client 
  • Preventing poisoning
  • Using third party components 
  • Fetch de-duplication

The Devil in the details

  • Props should be serializable
    • Okay: Number, String, Object, Array
    • Not okay: Functions
  • Network boundary: Props going from server to client component are crossing the network boundary

Devil in details: Server -> Client props

// Next.js
// client component called within server component
// with props

import { Counter as CounterOnClient } from './counter'

export function MyServerComponent() {
  const x = "hello"
  const fn = ()=>{}
  return (
    <>
      <CounterOnClient prop1={x} />   // Ok
      <CounterOnClient prop1={fn} />  // NOT OK
    </>
  );
}
  • Import fails
  • Server components can be used but not imported within client component
  • Component code can't go. Rendered component goes

Devil in details: Importing Server Component in Client

'use client'; 

import MyServerComponent from './ServerComponent'; // Fails here

export function CounterOnClient() {
  return (
    <>
    	<MyServerComponent />
    	// other stuff
    </>
  );
}
  • Server Component can be used via the render prop pattern
  • In a parent Server component, both can be imported
  • And then our MyServerComponent can be included as a child of the CounterLayout

Devil in details: Importing Server Component in Client

'use client'; 

// Server Component called as children

function CounterLayout({ children }) {
  return (
    <>
    	{ children }

        // other stuff
    </>
  );
}
// Next.js
// Using server components
// inside of client component
//

import MyServerComponent from './ServerComponent';
import { CounterLayout } from './counterLayout'

export function MainServerComponent() {
  return (
    <CounterLayout>
    	<MyServerComponent />
    </CounterLayout>
  );
}
  • Modules like these can be imported as usual in the client component. This can happen by accident.
  • This is poisoning
  • Only ServerComponents are prevented. Importing the rest is legal. 

Devil in details: Preventing poisoning

'use client'; 

// This is legal
import { myServerOnlyFn } from './utils'

function CounterLayout() {
  const value = myServerOnlyFn()

  return (
    <>
    	{ value }
        // other stuff
    </>
  );
}
//
// A function that should be called 
// on server side only
//

export function myServerOnlyFn() {
 
  // My
  // Super
  // secret
  // algorithm

  return Math.random();
}
  • Use the server-only package to mark a module as server-only
  • Build fails if client-component tries importing such a module
  • (Next.js only)

Devil in details: Preventing poisoning

'use client'; 

// This fails now
import { myServerOnlyFn } from './utils'

function CounterLayout() {
  const value = myServerOnlyFn()

  return (
    <>
    	{ value }
        // other stuff
    </>
  );
}
import "server-only"; // NPM package

//
// A function that should be called 
// on server side only
//

export function myServerOnlyFn() {
 
  // My
  // Super
  // secret
  // algorithm

  return Math.random();
}
  • Third party component libraries may fail in server components, because they may be interactive (useState)

  • If they have the 'use-client' declaration, then its okay

  • We can write wrapper components for such situation

Devil in details: Using third party components

// Next.js

import { SliderComponent } from 'slider-lib'

export function ServerComponent() {
  return (
    // Fails
    <SliderComponent />
  );
}

Devil in details: Using third party components

// Next.js

import { MySliderComponent } from './sliderWrapper'

export function ServerComponent() {
  return (
    // Works
    <MySliderComponent />
  );
}
// 
// Wrapper component
// 
'use-client'

import { SliderComponent } from 'slider-lib'

export function MySliderComponent() {
  return (
    <SliderComponent />
  );
}

The wrapper

Devil in details: Fetch de-duplication

  • Next.js recommendations:

    • fetch data in server components, not client

    • fetch in each (server) component that needs it.

  • Don't: fetch it all at once in a parent, and passing down as props. (Context is not available anyway)

  • Next.js performs a de-duplication on these requests

  • Benefit: reduced coupling

Server components

That's all

Made with Slides.com