Deploy a Next.js application
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
| Setting | Value | From |
|---|---|---|
| Framework | Next.js | The next dependency, and next.config.ts if present. |
| Node version | 24, or what engines.node in package.json admits | Only engines.node is read. 24 and 22 are supported. |
| Package manager | pnpm, npm or Yarn | The lockfile, or the packageManager field. |
| Build command | pnpm build, npm run build or yarn build | Your build script, which should run next build. |
| Start command | Your start script, or node_modules/.bin/next start when there is none | next start reads PORT. |
| Port | 3000 | A 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
getServerSidePropsreadsprocess.env.API_URLat 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.tsunderenv, 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 database → DATABASE_URL, then redeploy. In a route handler:
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.