Skip to content
Guides

Deploy a Next.js application

From a Next.js repository to a live URL, and where public and server values live when the build cannot see your variables.

Kuppit detects a Next.js application from the next dependency, builds it with your build script, and starts it with next start. The one thing to know before the first deployment is how Next.js treats NEXT_PUBLIC_ values, because it changes where they come from on Kuppit.

What Kuppit detects

SettingValueFrom
FrameworkNext.jsThe next dependency, and next.config.ts if present.
Node version24, or what engines.node in package.json admitsOnly engines.node is read. 24 and 22 are supported.
Package managerpnpm, npm or YarnThe lockfile, or the packageManager field.
Build commandpnpm build, npm run build or yarn buildYour build script, which should run next build.
Start commandYour start script, or node_modules/.bin/next start when there is nonenext start reads PORT.
Port3000A default; PORT overrides it at runtime.

Before you deploy

next start listens on PORT and on all interfaces, so the PORT contract is met as long as your start script is next start or absent. A custom server must read PORT and bind to 0.0.0.0 itself.

Do not set output: 'export': a static export has no server to start. output: 'standalone' is unnecessary; Kuppit runs the ordinary build.

Deploy

Follow the Quickstart: install the GitHub App, choose the repository and branch, confirm the proposal, and choose Deploy analyzed commit. The deployment reaches Live and the service's hostname opens your application.

Values the browser needs

Next.js inlines NEXT_PUBLIC_ variables into the client bundle at build time. Kuppit's variables reach the running server, not the build, so a NEXT_PUBLIC_API_URL set on the service's Variables tab is present in server code and absent from the bundle.

Two ways to handle it:

  • Read it on the server and pass it down. A Server Component, a route handler or getServerSideProps reads process.env.API_URL at request time, and the client receives it as a prop. This is the usual answer, and it means the value changes with a redeploy rather than a rebuild.
  • Commit it. A value that is the same for every environment and not secret can live in next.config.ts under env, or in a committed .env.production.

Server-side values, process.env.DATABASE_URL in a route handler, work as they do anywhere: set them on the Variables tab, redeploy, and they are there.

Add a database

On the environment's Overview, Add resource, PostgreSQL. Keep the name database. On the service's Variables tab, add DATABASE_URL as a Reference to databaseDATABASE_URL, then redeploy. In a route handler:

app/api/health/route.ts
import { Client } from 'pg'

export async function GET() {
    const db = new Client({ connectionString: process.env.DATABASE_URL })
    await db.connect()
    const { rows } = await db.query('select now()')
    await db.end()
    return Response.json(rows[0])
}

From here

Every push to the branch deploys again, and every pull request gets a preview environment. A build that fails is almost always in the Build log; Build failures walks through the messages.

Was this page helpful?