Framework guides

Deploy Next.js on Zenifra

Next.js runs on Zenifra as a full Node.js server: server rendering, API routes, Server Actions, middleware, and image optimization work without adapters.

Prerequisites

  • A GitHub repository with the Next.js project
  • package.json and package-lock.json committed at the repository root
  • GitHub account connected to Zenifra
  • The project runs locally with npm ci && npm run build && npm start

pnpm and Yarn

Install uses npm ci. If the project uses pnpm or Yarn, generate and commit a compatible package-lock.json, or deploy via OCI Image.

Console configuration

Create the project

In the console, click Create Project and choose GitHub Repository. Select the repository and branch.

Pick the runtime

Select Node.js and the same major version you use locally (20, 22, or 24). Check the engines field in package.json and the minimum version required by your Next.js release.

Fill in port and commands

FieldValue
Port3000
pre-buildempty
buildnpm run build
startnpx next start -H 0.0.0.0 -p 3000

Add variables

Add environment variables before creating the project, including NEXT_PUBLIC_*. See the next section.

Create and follow along

Click Create Project and watch the Build Logs tab. When the project is Running, open the *.clients.zenifra.com URL.

Environment variables in Next.js

Next.js treats two kinds of variables differently:

KindExampleWhen it is read
ServerDATABASE_URL, AUTH_SECRETAt runtime
PublicNEXT_PUBLIC_API_URLAt build time, embedded in browser JavaScript

On Zenifra, project variables are available during the build, so NEXT_PUBLIC_* works normally.

Changed a NEXT_PUBLIC_ value? Rebuild

Saving variables restarts the instances but does not recompile the code. NEXT_PUBLIC_* values already embedded for the browser only change after a new deployment: push a commit or trigger a manual deploy.

Never put secrets in NEXT_PUBLIC_* variables: every visitor can see them.

Health check

Create a lightweight route for the health check:

app/api/health/route.ts
export const dynamic = 'force-dynamic';

export function GET() {
  return Response.json({ status: 'ok' });
}

Set the path to /api/health in the project. If middleware.ts enforces authentication, exclude this route in the matcher so the check does not get redirected.

Database with Prisma or Drizzle

  1. Create a managed database and copy its connection URL.
  2. Add DATABASE_URL to the project variables.
  3. Make sure the ORM client is generated during the build. With Prisma, prisma generate is usually in postinstall; if not, use build = npx prisma generate && npm run build.
  4. Apply migrations at startup, when the app already has access to the project network:
start: npx prisma migrate deploy && npx next start -H 0.0.0.0 -p 3000

Multiple instances

With more than one instance, all of them run start. Prisma serializes migrate deploy with a database lock, but long migrations delay startup. For large changes, apply the migration beforehand from a machine with database access.

User uploads

The instance filesystem is ephemeral. Uploads saved to public/ or to disk disappear on every deployment. Use persistent storage with a directory outside the code, such as /app/uploads, or an object storage service.

Next.js cache and multiple instances

The Next.js data cache and ISR live on each instance's disk by default. With more than one instance, each keeps its own cache, and revalidatePath only affects the instance that received the call. If that matters to you, use one instance or configure a shared cacheHandler backed by the managed cache.

standalone output (optional)

output: 'standalone' reduces the size of the running app. If you enable it, adjust the commands:

build: npm run build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/
start: node .next/standalone/server.js

Also set the HOSTNAME=0.0.0.0 and PORT=3000 variables in the project, which server.js uses to listen.

Common problems

SymptomLikely causeFix
Build fails with Cannot find module 'typescript'Build dependencies removedMake sure the build field is filled in and package-lock.json is in sync
npm ci failsMissing or out-of-sync lockfileRun npm install, commit package-lock.json, and deploy again
Public URL does not respondDifferent port or server on localhostUse -H 0.0.0.0 -p 3000 and Port 3000
NEXT_PUBLIC_* has an old valueValue embedded in the previous buildDeploy again
next/image returns errorsRemote domain not allowedAdd the domain to images.remotePatterns
Slow build or out of memoryLarge projectReview the project plan in Limits

Next steps

Last updated on

On this page