SPA Mode
Static export served by NestJS: single process, single port
If your app doesn't need server-side rendering, you can build Next.js as a static export and serve it directly from NestJS. This means a single process and a single port in production: no reverse proxy, no separate web container.
How it works
next build with output: "export" emits static HTML/JS/CSS to apps/web/out/. NestJS uses ServeStaticModule to serve those files, while still handling /api/* routes through its controllers.
output: "export" is incompatible with rewrites(), so the config splits by environment, with rewrites in dev for the local proxy and static export in production:
next.config.js
const isProduction = process.env.NODE_ENV === 'production';
const nextConfig = {
...(isProduction
? { output: 'export' }
: {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:3001/api/:path*',
},
];
},
}),
};app.module.ts
import { join } from 'path';
import { ServeStaticModule } from '@nestjs/serve-static';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(process.cwd(), '../web/out'),
exclude: ['/api/(.*)'],
}),
// ...
],
})
export class AppModule {}Install the module:
pnpm add @nestjs/serve-static --filter=apiBuild and run
# 1. Build the static frontend
cd apps/web && next build
# 2. Build the API
cd apps/api && nest build
# 3. Start NestJS - it serves everything on port 3000
PORT=3000 node apps/api/dist/mainIn production, only the NestJS container needs to run. The Next.js build output in out/ should be present at ../web/out relative to where NestJS starts.
Limitations
No SSR, no Server Components with request-time data, no cookies() or headers() in server code. Static export is a build-time-only rendering model. If any page uses these features, next build will throw an error.