Cosmoner Docs
Guides

Deploy a Next.js App

Run a Next.js app on Cosmoner from your repository — server rendering, API routes and NEXT_PUBLIC_ variables included — or ship it as a static export.

Overview

A Next.js app deploys as a web service with no configuration: Cosmoner detects Next.js from your package.json, installs your dependencies, runs next build, and starts the app with next start. Server components, route handlers, middleware, image optimization and incremental static regeneration all work, because it is the same Node.js server you run locally.

If your app is fully static — output: 'export' — you can deploy it as a static site instead, for a flat price.

This guide covers what is specific to Next.js. For the deploy flow itself, see Deploy an App from Git.

Deploy it as a web service

  1. In your project, go to Apps → New app and choose a domain.
  2. Under Git Repository, connect your provider and pick the repository and branch. With a next.config.js or next.config.ts, the Automatic strategy is labelled Detected: Next.js; any other config file builds the same way without the label.
  3. Leave the app type as Web Service and the strategy as Automatic. Build command and Run command are detected from your package.json — the build and start scripts — so leave them empty unless you use different ones.
  4. Leave Port empty. next start listens on the port in PORT, which the app is given.
  5. Add your environment variables (see below), then Continue, choose a plan and Launch.

For a monorepo, set App directory to the Next.js app's folder, e.g. apps/web.

Choosing a plan

next build runs on the build machine, not on your plan, so the plan only has to fit the running server. Shared S (1 GB of memory) is a comfortable start for most apps; move up when Metrics shows memory close to the limit or restarts.

Environment variables

Next.js reads variables at two different times, and Cosmoner provides both:

VariableRead whenWhat to do
NEXT_PUBLIC_*At build time, and written into the JavaScript sent to the browser.Add it to the app. It is available to next build. Changing it needs a new build — Redeploy after saving.
Everything elseAt run time, on the server only.Add it to the app. Changing it takes effect on the next deploy.

Anything prefixed NEXT_PUBLIC_ is public by design — it ends up in your users' browsers. Never put a secret behind that prefix. Keep secrets such as API keys and database URLs unprefixed, and link them from the vault with Link secret so their values never sit in the app's settings — see Secrets.

Deploy it with your own Dockerfile

Use Dockerfile as the strategy when you want full control over the image, for example with Next.js's standalone output. Two things differ from building locally:

  • Build-time variables arrive as build arguments. Declare each NEXT_PUBLIC_* variable your build needs with ARG before next build, or it will be empty in the bundle.
  • Listen on all interfaces. The standalone server binds to HOSTNAME; set it to 0.0.0.0.
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
ARG NEXT_PUBLIC_API_URL
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production 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"]

With output: 'standalone' set in next.config. The server listens on PORT when it is set, which it always is on Cosmoner; EXPOSE 3000 is only used when no port is stated anywhere else.

Variables you mark as secret are not passed as build arguments. They are mounted for the build step instead, which keeps them out of the image layers: read one with RUN --mount=type=secret,id=NAME,env=NAME.

Static export

An app built with output: 'export' produces plain files and can be deployed as a Static Site:

  1. In next.config, set output: 'export'. Pages are found at their clean URLs either way — /about serves about.html — so trailingSlash is your choice.
  2. In the wizard, choose Static Site.
  3. Set Build command to npm ci && npm run build — a static build starts from a clean Node.js environment, so it has to install dependencies itself.
  4. Set Output directory to out.

A static export cannot use server-side features: route handlers, server actions, middleware, or image optimization at request time. Dynamic routes need generateStaticParams so every page exists as a file.

Connecting a database or cache

Store the connection string as a secret and link it as an environment variable, e.g. DATABASE_URL or REDIS_URL, then read it from process.env in server code only. See Connect to Redis for a cache.

Keep the settings in your repository

Commit a .cosmoner/deployment.yaml and the wizard fills itself in from it:

# yaml-language-server: $schema=https://cosmoner.com/schemas/app.schema.json
services:
  - name: web
    type: service
    autodeploy: true
    envs:
      - key: NEXT_PUBLIC_API_URL
        value: https://api.example.com
      - key: DATABASE_URL
        from_secret: DATABASE_URL

See App Configuration Files for every field.

Troubleshooting

NEXT_PUBLIC_ values are undefined in the browser. The variable was added after the last build, or — with a Dockerfile — is not declared with ARG. Add it, then Redeploy.

The deploy never becomes healthy with a Dockerfile. The server is listening on a port or interface other than the one it was given. Check that HOSTNAME=0.0.0.0 is set and that nothing overrides PORT.

Pages of a static export return 404. Check that Output directory is out and that the page exists as a file in it — dynamic routes need generateStaticParams.

On this page