A conventionally deployed Next.js project needs the entire node_modules folder on the server, including hundreds of build-only packages such as TypeScript, linters and test libraries. On a small VPS that wastes disk space, slows deploys and bloats Docker images. Standalone mode traces the files the app actually uses during the build and collects them into a separate folder.
How standalone works
During the build Next.js analyses what every page and API route imports. With standalone enabled you get .next/standalone, containing a minimal server.js and a trimmed node_modules. It runs with plain node server.js, so only this folder has to reach the server.
Enabling it
// next.config.mjs
const nextConfig = {
output: "standalone",
};
export default nextConfig;
Rebuild, then compare sizes with du -sh node_modules .next/standalone. The difference depends on the project but is usually noticeable. You will find server.js, a package.json, the trimmed node_modules and an inner .next folder inside.
The forgotten step: public and static
Two things are not copied automatically: the public folder and the CSS and JavaScript chunks in .next/static. Next.js assumes a CDN or Nginx will serve them. Without them the site loads unstyled, without images and with dead buttons. Add the copy to a postbuild script so it never gets skipped.
"scripts": {
"build": "next build",
"postbuild": "cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/"
}
Running on the server
The server reads PORT and HOSTNAME from the environment. Use 127.0.0.1 behind Nginx and 0.0.0.0 inside Docker. For permanent operation, run it under PM2.
// ecosystem.config.js
module.exports = {
apps: [{
name: "misol-next",
cwd: "/var/www/misol-next/.next/standalone",
script: "server.js",
max_memory_restart: "400M",
env: { NODE_ENV: "production", PORT: 3000, HOSTNAME: "127.0.0.1" }
}]
};
Building off the server
You can build on a powerful machine or in CI and ship the finished folder, so the small VPS never runs out of memory during builds. The build machine must match the server's OS and CPU architecture, because packages such as sharp ship platform-specific binaries.
npm run build
tar -czf release.tar.gz -C .next/standalone .
scp release.tar.gz deploy@example.uz:/var/www/misol-next/releases/
Docker
A multi-stage Dockerfile builds in the first stage and copies only the result into the final image.
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
Example: a furniture shop in Namangan
A furniture shop in Namangan runs its Next.js catalogue on a 1 GB VPS. Builds used to run on the server, memory ran out every time and the site went down for minutes. With standalone, the developer builds locally, uploads an archive and PM2 reloads the app. The server now holds only the required packages, and a deploy takes under a minute.
Common problems
- Unstyled pages:
.next/staticwas not copied. - Images and favicon return 404:
publicwas not copied. - Docker container unreachable:
HOSTNAME=0.0.0.0is missing. - Environment variables ignored:
.envfiles are not copied into standalone, so set them on the server.