Next.js Output Standalone Configuration: Implications for Custom Server Setups

When you enable output: 'standalone' in Next.js, the framework generates a self-contained runtime in .next/standalone that requires launching via node .next/standalone/server.js instead of next start, serializes configuration into the __NEXT_PRIVATE_STANDALONE_CONFIG environment variable, and restricts bundler flags while maintaining compatibility with custom server wrappers.

The nextjs output standalone configuration fundamentally changes how Next.js applications are deployed, particularly when you use a custom server setup. In the standard Vercel/next.js repository, this mode creates a minimal, production-ready bundle by tracing only the files actually required at runtime. Understanding these implications ensures your custom server integration remains functional while leveraging the benefits of reduced deployment artifacts.

How output: 'standalone' Changes the Runtime Model

When you configure output: 'standalone' in next.config.js, the build process generates a specialized directory structure that differs significantly from the default build output.

The framework creates .next/standalone containing only the essential runtime components: the compiled server code, traced node_modules dependencies, and a minimal package.json. This contrasts with the default behavior where the entire .next directory and source tree must be present for next start to function.

Aspect Default Build output: 'standalone'
Entry point next start reads the full .next build and launches next-server.js next start prints a warning and exits; you must use node .next/standalone/server.js
Configuration loading next start loads next.config.js at runtime Build writes resolved config to __NEXT_PRIVATE_STANDALONE_CONFIG; server reads this JSON
Custom server API import next from 'next' creates a NextCustomServer wrapper Same wrapper works, but rejects webpack, turbo, or turbopack flags
File availability All project files accessible at runtime Only traced files copied to standalone folder; missing imports crash the app

Entry Point and Configuration Handling

The New Binary Entry Point

In packages/next/src/server/next.ts, the framework explicitly checks for standalone mode. If you attempt to run next start after a standalone build, the CLI detects this configuration and emits a warning before exiting at lines 319-326:

// packages/next/src/server/next.ts#L319-L326
if (process.env.__NEXT_PRIVATE_STANDALONE_CONFIG) {
  console.warn(
    `"next start" does not work with "output: standalone" configuration.\n` +
    `Use "node .next/standalone/server.js" instead.`
  )
  process.exit(0)
}

The correct entry point becomes the pre-built binary at .next/standalone/server.js, which contains the fully compiled Next.js server ready for immediate execution.

Configuration Serialization via Environment Variable

The standalone architecture serializes the resolved configuration to avoid re-executing next.config.js at runtime. During the build process, packages/next/src/build/utils.ts writes the configuration JSON to the environment variable __NEXT_PRIVATE_STANDALONE_CONFIG at lines 1465-1475.

At runtime, packages/next/src/server/config.ts reconstructs the configuration object by parsing this environment variable at lines 1545-1559:

// packages/next/src/server/config.ts#L1545-L1559
if (process.env.__NEXT_PRIVATE_STANDALONE_CONFIG) {
  const standaloneConfig = JSON.parse(
    process.env.__NEXT_PRIVATE_STANDALONE_CONFIG
  )
  // Merge standaloneConfig with defaults...
}

This mechanism ensures configuration consistency while eliminating the need for the runtime environment to access the original next.config.js file.

Custom Server Compatibility

Using the Next.js Wrapper API

Custom servers continue to function with output: 'standalone' through the standard next() wrapper API. In packages/next/src/server/next.ts, the createServer() function instantiates NextCustomServer when options.customServer is true (the default when importing next programmatically).

However, the standalone build imposes specific restrictions on server initialization. At lines 48-66 in packages/next/src/server/next.ts, the code validates that certain options are not passed when running in standalone mode:

// packages/next/src/server/next.ts#L48-L66
if (options.customServer) {
  // Validate that webpack/turbopack options aren't passed in standalone mode
  if (process.env.__NEXT_PRIVATE_STANDALONE_CONFIG) {
    if (options.webpack || options.turbo || options.turbopack) {
      throw new Error(
        `Cannot use webpack() or turbo() options in custom server with output: 'standalone'`
      )
    }
  }
}

Restricted Bundler Flags

The standalone output pre-compiles all assets during the build phase, making runtime bundler configuration impossible. When using a custom server with output: 'standalone', you cannot pass webpack, turbo, or turbopack options to the next() constructor. The framework explicitly throws an error if these options are detected in the standalone environment, as the bundler has already executed and finalized the build artifacts.

File Tracing and Runtime Isolation

The standalone output relies on aggressive file tracing to determine exactly which files are required at runtime. During the build, packages/next/src/build/collect-build-traces.ts analyzes the server code to generate a manifest of dependencies.

Only files captured by this tracing process are copied into .next/standalone. This includes specific node_modules files and project assets, but excludes the full source tree. If your custom server or application code performs dynamic imports or requires files not detected by the static analysis, those files will be missing at runtime, causing the server to crash.

This tracing mechanism enables the minimal Docker image size characteristic of standalone deployments, but requires careful attention to dynamic code paths.

Practical Implementation Examples

Custom Server with Standalone Output

When using a custom server alongside output: 'standalone', you maintain the standard Next.js wrapper pattern but ensure the server is started via the standalone binary:

// server.js (custom server)
import next from 'next';
import http from 'http';

const app = next({ 
  dev: false,
  // Note: cannot use webpack(), turbo(), or turbopack() options here
});

await app.prepare();
const handler = app.getRequestHandler();

http.createServer((req, res) => {
  handler(req, res);
}).listen(3000, () => {
  console.log('> Ready on http://localhost:3000');
});

After building, you launch this through the standalone entry point, not next start.

Building and Running the Standalone Server


# Build the application with standalone output

npm run build

# Correct way to start in production

node .next/standalone/server.js

# Incorrect - will print warning and exit

# next start

Docker Deployment with Custom Server


# Multi-stage build for minimal standalone image

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build  # Generates .next/standalone

FROM node:20-alpine
WORKDIR /app

# Copy only the standalone output and package.json

COPY --from=builder /app/.next/standalone ./.next/standalone
COPY --from=builder /app/package.json ./

# No source code needed - everything is traced and bundled

CMD ["node", ".next/standalone/server.js"]

This configuration eliminates the need for the full Next.js CLI in the production container, running only the compiled standalone binary.

Summary

  • Entry point change: output: 'standalone' requires starting the application via node .next/standalone/server.js instead of next start, which explicitly warns and exits in this mode.
  • Configuration serialization: The build process writes the resolved next.config.js to __NEXT_PRIVATE_STANDALONE_CONFIG, which the standalone server reads at runtime in packages/next/src/server/config.ts.
  • Custom server restrictions: While the next() wrapper API remains compatible, you cannot pass webpack, turbo, or turbopack options to the custom server when using standalone output, as enforced in packages/next/src/server/next.ts.
  • File tracing isolation: Only files detected by the build tracer (packages/next/src/build/collect-build-traces.ts) are included in .next/standalone, requiring careful handling of dynamic imports.
  • Docker optimization: The standalone output enables minimal container images by excluding the Next.js CLI and source code, running only the pre-compiled server binary.

Frequently Asked Questions

Can I use next start with output: 'standalone'?

No. When output: 'standalone' is configured, running next start causes the CLI to print a warning and exit immediately. The code in packages/next/src/server/next.ts explicitly checks for the __NEXT_PRIVATE_STANDALONE_CONFIG environment variable and aborts with instructions to use node .next/standalone/server.js instead.

How does the standalone server load next.config.js?

The standalone server does not re-execute next.config.js at runtime. During the build, packages/next/src/build/utils.ts serializes the resolved configuration object and stores it as a JSON string in the __NEXT_PRIVATE_STANDALONE_CONFIG environment variable. At runtime, packages/next/src/server/config.ts parses this variable to reconstruct the configuration, ensuring consistency without requiring the config file in the production environment.

Can I still use a custom Express server with output: 'standalone'?

Yes, custom servers using the next() wrapper API remain compatible, but with specific constraints. You can still create a custom server using import next from 'next' and app.getRequestHandler(), but you must start the application via node .next/standalone/server.js rather than your custom server file directly if you want the standalone benefits. Additionally, you cannot pass webpack, turbo, or turbopack options to the next() constructor, as these are rejected in packages/next/src/server/next.ts when running in standalone mode.

What files are included in the .next/standalone folder?

The .next/standalone folder contains only the files traced as necessary for runtime execution. During the build, packages/next/src/build/collect-build-traces.ts analyzes the server code to determine exact dependencies. Only the compiled server, specific node_modules files, and assets referenced by the tracer are copied. Source files, the full Next.js CLI, and any dynamically imported modules not caught by the static analysis are excluded, which is why dynamic imports require careful handling in standalone deployments.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →