• Blog
  • Documentation
  • Courses
  • Changelog
  • AI Starters
  • UI Kit
  • FAQ
  • Supamode
    New
  • Pricing

Launch your next SaaS in record time with Makerkit, a React SaaS Boilerplate for Next.js and Supabase.

Makerkit is a product of Makerkit Pte Ltd (registered in the Republic of Singapore)Company Registration No: 202407149CFor support or inquiries, please contact us

About
  • FAQ
  • Contact
  • Verify your Discord
  • Consultation
  • Open Source
  • Become an Affiliate
Product
  • Documentation
  • Blog
  • Changelog
  • UI Blocks
  • Figma UI Kit
  • AI SaaS Starters
License
  • Activate License
  • Upgrade License
  • Invite Member
Legal
  • Terms of License

Server-only Code in Next.js 15

Dec 10, 2024

Ensure your Next.js code is only executed on the server-side using these lesser known Next.js features

next

Next.js has revolutionized React development by introducing a powerful Server Components architecture within its App Router. This architecture brings new considerations for developers, particularly around code organization and execution context.

However, the boundaries between server and client code have become more blurred, and developers are often caught unaware by potential pitfalls concerning where the code is being executed. This can lead to pretty bad outcomes.

In this guide, we'll explore how to effectively manage the separation between server and client code in your Next.js applications, how to avoid leaking secrets and ensuring your code is only ever executed server-side.

The Foundation: Server vs Client Components

Before diving into the specifics of avoiding bundling server side code into the client, we need to understand the main culprits that lead to this problem.

With the introduction of Next.js App Router and React Server Components, we can now write React Components that are exclusively executed server-side. Here's the thing: data fetching also happens inside these components, which can lead to server-side data making its way to the client.

Data Fetching in Server Components

Let's take a look at an example of data fetching in a Server Component:

  1. A DashboardPage Server Component
  2. A Chart Client Component (eg. it uses use client, which means the component will both be rendered on the server and the client)
tsx
const apiSecret = process.env.API_SECRET;
async function DashboardPage() {
const data = await fetchData(apiSecret);
return <Chart data={data} />;
}

This server component fetches data from an API and renders a chart based on that data. It uses a secret key called API_SECRET to authenticate the request.

If we changes this slightly, we can see that the secret key is now leaked to the client as we mistakenly pass the apiSecret to the client component as a prop:

tsx
const apiSecret = process.env.API_SECRET;
async function DashboardPage() {
const data = await fetchData(apiSecret);
return <Chart data={{data, apiSecret}} />;
}

The apiSecret is now leaked to the client, which is not ideal. As uncommon a mistake as this may seem, you'd be surprised how many people make this mistake.

So, kinda bad, right? Let's take a look at how we can avoid this.

Better conventions for server-side code

The first practical step is to ensure that all server-side code is properly separated from the client.

In Makerkit, a SaaS Starter Kit for Next.js and Supabase, the convention is to always create a lib/server directory for server-side code, whether that's in a package or in the main app directory.

text
- components
- ... (all your components)
- lib
// Zod schemas, shared between server and client
- schema
// server-side code
- server
- auth.service.ts
// any shared code
- utils

This doesn't prevent you from accidentally importing server-side code into the client, but it does make it more explicit that you're aware of the boundaries between server and client code.

💡 When you write server-side code, always make sure to be explicit about where it's being executed.

Using the 'server-only' Package

The very best way to avoid accidentally importing server-side code into the client is to use the server-only package.

tsx
import 'server-only';
const apiSecret = process.env.API_SECRET;
export function fetchData() {
// sensitive operation, only executed on the server
const data = db.getData(apiSecret);
return data;
}

If you accidentally import a component using this file, you'll get a runtime error - and you'll know that you're doing something wrong.

💡 Add all server-side code into files that are marked with the server-only package.

Using the Taint API

The experimental Taint API is a new React API that allows you to mark values as tainted, which means they're not safe to pass to the client.

This is useful when you want to ensure that a value is only used on the server, and not on the client.

We can think of it asn additional layer of security, as it ensures that the value is only used on the server, and not on the client.

tsx
import {
experimental_taintUniqueValue as taintUniqueValue
} from 'react/experimental';
const apiSecret = taintUniqueValue(
'The API secret is only used on the server',
process,
process.env.API_SECRET,
);
export function fetchData() {
// sensitive operation, only executed on the server
const data = db.getData(apiSecret);
return data;
}

This alone is not enough to ensure that the value is only used on the server, since there are ways to bypass the taint API.

tsx
const key = taintUniqueValue(
'The API secret is only used on the server',
process,
process.env.API_SECRET,
);
const derivedKey = key.toUpperCase();

The constant key is now tainted, however, the derivedKey is not. This means that the value is still accessible on the client if it was accidentally passed to the client.

💡 Consider using the taint API to ensure that values are only used on the server, but do not solely rely on it to prevent leaking secrets.

Summarizing best practices

Here are the best practices we've discussed in this guide:

  1. Code Organization: Always ensure that all server-side code is properly separated from the client at the folder level.
  2. Place server code in their own files: Use the server-only package to ensure that server-side code is only ever executed on the server.
  3. Use the taint API to ensure that values are only used on the server, but do not solely rely on it to prevent leaking secrets.

While bundlers have gotten better at optimizing code, they're not perfect.

By following these best practices, you can build more secure, performant, and maintainable applications that leverage the full power of Next.js's Server Components.

Conclusion

In this guide, we've explored the importance of establishing clear boundaries between server and client code in Next.js applications. We've also discussed how to avoid leaking secrets and ensure that server-side code is only ever executed on the server.

By following these best practices, you can build more secure, performant, and maintainable applications that leverage the full power of Next.js's Server Components.

Some other posts you might like...
Jun 9, 2025Claude Code: Build a SaaS with AIThis is a step-by-step guide to building an AI Content Repurposer SaaS by vibe-coding with Claude Code and Makerkit.
Apr 23, 2025Next.js Security: A Comprehensive Guide how to secure your Next.js applicationA comprehensive guide to securing your Next.js application, focusing on practical strategies to protect your application and user data from common vulnerabilities.
Jan 17, 2025Best Practices for Building a SaaS with Windsurf and MakerkitWindsurf is a new AI-powered editor taking the developer experience to the next level. With the new optimized rules for Makerkit, building a SaaS just got a lot easier!
Jan 16, 2025Best Practices for Building a SaaS with Cursor and MakerkitCursor is the hottest AI editor in the market. With the new optimized rules for Makerkit, building a SaaS just got a lot easier!
Dec 26, 2024Choosing the best hosting provider for your Next.js applicationIn this post, we'll show you how to choose the best hosting provider for your Next.js application.
Dec 24, 2024Next.js API Routes: The Ultimate GuideLearn how to build robust, secure, and efficient API endpoints in Next.js. This comprehensive guide will walk you through the critical best practices for creating robust, secure, and efficient API endpoints in Next.js.