Course
Best Practices
Next.js Mastery: From Fundamentals to Full-Stack
Unlock the power of Next.js with this comprehensive course! Starting with the basics, you’ll learn essential skills such as routing, data fetching, and styling. Progress through practical projects, including building your own React Notes app, to gain hands-on experience. Dive into the source code to understand the inner workings and core principles of Next.js. Perfect for developers with a basic knowledge of React and Node.js, this course ensures you’ll be ready to create high-performance full-stack applications efficiently. Join us and master Next.js, backed by industry experts and a supportive learning community.
Best Practices
Using Server Components
Sharing Data
When fetching data on the server, you might encounter scenarios where multiple components need to share the same data. In such cases, you don't need to use React Context (which isn't usable on the server side) or pass data through props. You can simply request the data in the components that need it. This is possible because React has extended the fetch function to include a caching mechanism. If the same request with the same parameters is made, the data will be cached.
async function getItem() { const res = await fetch('https://.../item/1') return res.json()}
// Function is called twice, but only the first call executesconst item = await getItem() // cache MISS
// Second call uses the cacheconst item = await getItem() // cache HIT
Note that this caching has certain limitations, such as being applicable only for GET requests. We'll cover the details and principles of this in the caching section.
Server-Only Components
Because JavaScript modules can be shared between server and client component modules, you might want to ensure that a module is used only on the server. For instance, consider this code:
export async function getData() { const res = await fetch('https://external-service.com/data', { headers: { authorization: process.env.API_KEY, }, })
return res.json()}
This function uses an API_KEY, so it should only be used on the server. If used on the client, Next.js will replace private environment variables with empty strings to prevent leaks. Therefore, this code can be imported and executed on the client but won't run as expected.
To prevent server-only code from being used on the client accidentally, you can use the
server-only
package. This package will throw a build error if server-only code is used on the client.First, install the package:
npm install server-only
Then, import the package in the server-only component code:
import 'server-only'
export async function getData() { const res = await fetch('https://external-service.com/data', { headers: { authorization: process.env.API_KEY, }, })
return res.json()}
Now, any client component importing
getData
will throw an error during the build process, ensuring the module is used only on the server.Using Third-Party Packages
React Server Components is a new feature, and many packages in the React ecosystem may not yet fully support it. This can cause issues.
For example, if you use the
acme-carousel
package, which exports a <Carousel />
component that uses useState
but doesn't have a "use client" declaration, it will work fine in a client component:'use client'
import { useState } from 'react'import { Carousel } from 'acme-carousel'
export default function Gallery() { let [isOpen, setIsOpen] = useState(false)
return ( <div> <button onClick={() => setIsOpen(true)}>View pictures</button>
{/* Works, since Carousel is used within a Client Component */} {isOpen && <Carousel />} </div> )}
However, if you use it in a server component, it will throw an error:
import { Carousel } from 'acme-carousel'
export default function Page() { return ( <div> <p>View pictures</p>
{/* Error: `useState` cannot be used within Server Components */} <Carousel /> </div> )}
To resolve this issue, wrap the third-party component in your own client component:
'use client'
import { Carousel } from 'acme-carousel'
export default Carousel
Now, you can use
<Carousel />
in a server component:import Carousel from './carousel'
export default function Page() { return ( <div> <p>View pictures</p> <Carousel /> </div> )}
4. Using Context Provider
Contexts are typically used for global state sharing, such as the current theme (for implementing theme switching). However, server components do not support React context. If you try to create context directly, it will throw an error:
import { createContext } from 'react'
// Server components do not support createContextexport const ThemeContext = createContext({})
export default function RootLayout({ children }) { return ( <html> <body> <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider> </body> </html> )}
To solve this issue, create and render the context in a client component:
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext({})
export default function ThemeProvider({ children }) { return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>}
Then use it at the root level:
import ThemeProvider from './theme-provider'
export default function RootLayout({ children }) { return ( <html> <body> <ThemeProvider>{children}</ThemeProvider> </body> </html> )}
Now other client components in the application can use this context.
Using Client Components
Move Client Components Down the Tree
To minimize the size of the client-side JavaScript bundle, try to move client components as far down the component tree as possible.
For example, if you have a layout with some static elements and an interactive search bar that uses state, you don't need to make the entire layout a client component. Extract the interactive logic into a client component (like
<SearchBar />
) and keep the layout as a server component:// SearchBar Client Componentimport SearchBar from './searchbar'// Logo Server Componentimport Logo from './logo'
// Layout remains as a Server Componentexport default function Layout({ children }) { return ( <> <nav> <Logo /> <SearchBar /> </nav> <main>{children}</main> </> )}
Data Passed from Server to Client Components Must Be Serializable
When you fetch data in a server component and pass it down to a client component as props, this data needs to be serializable.
This is because React needs to serialize the component tree on the server and deserialize it on the client to reconstruct the tree. If you pass non-serializable data, it will cause errors.
If you cannot serialize the data, consider fetching the data in the client component using a third-party package.
References
- Introducing Zero-Bundle-Size React Server Components – React Blog
- How React Server Components Work: An In-Depth Guide
- Rendering: Server Components
- Rendering: Client Components
- Rendering: Composition Patterns
- Discussion on Hacker News
- The Future of Web Development
- Why RSC: An Overview