Server-only code in Next.js App Router
The React Server Components architecture, which separates components into Client and Server types, has been integrated with Next.js's App Router. When using the app router, it's crucial to distinguish between server-only and client-side code to ensure application security, performance, and maintainability. This blog post explains how to define server-only code in a Next.js application.
Understanding Server-only code
In Next.js, server-only code refers to code intended to run exclusively on the server. This includes:
- Using server-specific libraries.
- Accessing environment variables with sensitive information.
- Interacting with databases or external APIs.
- Processing confidential business logic.
A challenge arises because JavaScript modules can be shared between server and client components, potentially leading to unintentional inclusion of server-side code in the client bundle. This can expose sensitive data, increase the bundle size, and create security vulnerabilities.
Practical Example
Let's go through an example to illustrate server-only code. Start by creating a Next.js application
using create-next-app
. Then, in a new src/utils
directory, add a file named server-utils.ts
with the following server-side function:
// src/utils/server-utils.ts
export const serverSideFunction = () => {
console.log(
`Using multiple libraries,
accessing environment variables,
interacting with a database,
processing confidential information`,
)
return 'Server-side result'
}
This function uses various NPM packages, accesses API keys, retrieves data from a database, and processes sensitive algorithms, which should never be exposed to the client side.
Implementing Server-only code
Next, use this function in a server component, such as an About page component:
// pages/about.tsx
import { serverSideFunction } from '@/utils/server-utils'
const About = () => {
const result = serverSideFunction()
console.log(result)
return <div>About Page</div>
}
export default About
When you navigate to the /about
route, you'll see log messages in the server terminal, not in the
browser, indicating that the code is server-only.
However, if this function is mistakenly imported into a client component, like a Dashboard page, it may not work properly. This is where the server-only package helps.
Using the Server-only package
To protect your application, install the server-only package:
yarn add server-only
Then, update server-utils.ts
to include this package:
// src/utils/server-utils.ts
import 'server-only'
export const serverSideFunction = () => {
console.log(
`Using multiple libraries,
accessing environment variables,
interacting with a database,
processing confidential information`,
)
return 'Server-side result'
}
Now, if someone tries to import serverSideFunction
into a client component, the build process will
throw an error, preventing server-side code from leaking to the client.
Client-only package
Just as server-only code needs isolation, client-only code that interacts with browser-specific features like the DOM, the window object, and localStorage must be confined to the client side. The client-only package ensures that client-side code remains where it belongs.
Conclusion
Maintaining a clear boundary between server-only and client-side code is essential in Next.js applications. It ensures your application's integrity, security, and user experience. Use the server-only and client-only packages and follow best practices to enforce this separation and protect your application.