Server-only code in Next.js App Router

Next.js network boundary

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.