TutorialApp Hosting

How to Deploy a Next.js App

Next.js apps are more than static files: pages render on a server, API routes answer requests and middleware runs on every visit. Chajio Cloud runs your app exactly the way next start does on your computer, so all of that works as-is. This guide takes you from a GitHub repository to a live HTTPS address, then onto your own domain.

CChajio Cloud team September 25, 2026 9 min read
On this page

Before you start

  • A Chajio Cloud App Hosting plan. The Free plan is enough to follow along. Once you order, you get an email with your sign-in details, and the first time you sign in you choose your own password.
  • Your code on GitHub, in a repository you own or can access. A public repository you don't own works too.
  • A Next.js app with build and start scripts in package.json (projects made with create-next-app already have them) and its lockfile committed. Both the App Router and the Pages Router work.

Step 1: Get your app ready

For most apps there is nothing to change. Chajio Cloud installs your dependencies, runs npm run build, then starts your app with npm run start. That runs next start, which listens on the port the platform hands it in the PORT variable and accepts connections from outside the machine, which is exactly what’s needed.

Do choose your Node.js version, because without it your app builds on an older default. Recent versions of Next.js need a recent Node.js. Add a .nvmrc file to the root of your repository:

.nvmrc
22

Your scripts should look like this:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

Static export

If your next.config sets output: 'export', your app is a set of static files and next start won’t run. Serve the out folder instead: install serve and add a Procfile containing web: npx serve -s out -l tcp://0.0.0.0:$PORT, as described in the React guide.

Step 2: Create a project

A project is the home for one app, along with its database and domain. In your dashboard, open Projects and click New project:

Name
Lowercase letters, numbers and hyphens, up to 24 characters. It becomes part of your app’s free web address and can’t be changed later, so pick something short, like shop or api.
Plan
The plan the project draws its resources from.
Resources
CPU, memory and storage for this project, pre-filled with what your plan has left. The defaults suit most apps.

Click Create project. Setting it up takes about a minute, and you land on the deploy guide straight away, which walks you through three short steps: Source, Configure and Deploy. You can pick your repository while the project finishes setting up.

Step 3: Connect your repository

On the Source step, choose where your code comes from:

Your repository
Click Connect GitHub and approve access, which lets Chajio Cloud read your code and hear about your pushes. Then choose the repository and the branch to deploy, usually main. From now on, every push to that branch deploys automatically.
Public repository
Paste the URL of any public GitHub repository, no GitHub sign-in needed. Since you don’t own it, pushes can’t trigger deploys: deploy by hand, or tick Auto-deploy new releases to follow the project’s published releases.

If your app lives in a subfolder of the repository, as in a monorepo, set Build context to that folder, for example apps/web. Otherwise leave it as ., the repository root. Then click Connect repository and Continue.

Step 4: Configure

The Configure step has two runtime settings, both changeable later without a rebuild, and your environment variables.

Container port
Leave it at 3000. next start uses the port the platform passes it, so the two always match.
Health check path
/ works if your home page loads for a visitor who isn’t signed in. If it redirects to a login page or is slow to render, add a small health route and use /api/health instead:
app/api/health/route.ts
export function GET() {
  return Response.json({ ok: true });
}

Two kinds of environment variables

Next.js treats variables differently depending on their name, and it matters for how you change them:

  • Server variables, such as DATABASE_URL, STRIPE_SECRET_KEY or AUTH_SECRET. They are read by server code, API routes and middleware, and never reach the browser. This is where secrets go.
  • Variables starting with NEXT_PUBLIC_, such as NEXT_PUBLIC_API_URL. Next.js writes their values into the JavaScript sent to browsers when it builds. Anyone can read them, so they are for public values only, and a new value needs a new build.
DATABASE_URL=postgresql://...
AUTH_SECRET=a-long-random-string
NEXT_PUBLIC_API_URL=https://api.example.com

Variables live on the project's Environment tab. Add each one as a name and a value, then click Save & restart: your app restarts with the new values in moments, no rebuild needed.

Your build receives the variables too. A Dockerfile gets the ones it declares with an ARG line, and an auto-detected build gets all of them. A variable that went into your current build is marked Build. Change one of those and the dashboard offers to Redeploy, because only a new build picks up the new value.

PORT is set by the platform from the container port, so you can't set it yourself. If you attach a database, its connection variable appears here too, locked, because the platform manages it for you.

In practice: after changing a NEXT_PUBLIC_ variable, or a server variable that a page reads while it’s being built, accept the Redeploy prompt. A server secret that your code reads on each request only needs the restart.

Using Auth.js or NextAuth?

Set AUTH_SECRET (or NEXTAUTH_SECRET) and AUTH_URL (or NEXTAUTH_URL) to your app’s https address, and add AUTH_TRUST_HOST=true. Otherwise sign-in fails behind a hosting platform’s HTTPS layer with an “untrusted host” error.

Click Save and continue.

Step 5: Deploy

The Deploy step shows a summary of the repository, branch and build context. Click Deploy now and the build log starts streaming: your code is fetched, dependencies are installed and your app is built and started. The deployment moves through Queued, Building and Deploying to Success.

The first build takes a few minutes. Later builds reuse the parts that didn't change, so they are usually quicker. When it finishes, your app is live on a free live.chajio.cloud address with HTTPS already switched on. You'll find the address on the project's Overview.

If the deployment fails, the dashboard names the stage that broke and why, and the build log shows the details. Our troubleshooting guide covers the usual causes.

Adding a database

On plans that include one, a managed PostgreSQL or MariaDB database can live right next to your app. When you create it, give it the variable name your app expects, usually DATABASE_URL, and the connection string is handed to your app automatically. If you use Prisma, apply migrations as your app starts:

package.json
{
  "scripts": {
    "build": "prisma generate && next build",
    "start": "prisma migrate deploy && next start"
  }
}

Prisma takes a lock while it migrates, so two copies of your app starting together is safe. The full walkthrough is in How to Add a Database to Your App.

Using your own Dockerfile (optional)

You don’t need a Dockerfile for Next.js, but if you want control over the image, Next.js’s standalone output makes a small one. Enable it in your config:

next.config.js
module.exports = {
  output: 'standalone',
};
Dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Each ARG receives the Environment variable with the same name.
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/public ./public
# Owned by node: the server writes its image cache under .next at runtime.
COPY --from=build --chown=node:node /app/.next/standalone ./
COPY --from=build --chown=node:node /app/.next/static ./.next/static
USER node
CMD ["node", "server.js"]

The standalone server reads PORT itself. Declare an ARG for every NEXT_PUBLIC_ variable, because a Dockerfile build only receives the variables it declares. Server secrets don’t need an ARG: they arrive when the app runs, which keeps them out of the image. If your project has no public folder, remove that COPY line. More in our Dockerfile guide.

Connect your domain

Your app already works on its free address. To serve it from your own domain, open the project's Domains tab:

  1. Enter your domain, for example www.example.com, and click Add domain.
  2. The dashboard shows two DNS records to add where your domain's DNS is managed. A TXT record proves the domain is yours. A CNAME record routes visitors to your app, or an A or ALIAS record for a root domain like example.com. Copy the values exactly as shown.
  3. Click Verify. Once both records check out, a free HTTPS certificate is issued automatically, usually in under a minute, and your app is live on your domain.

DNS changes can take a while to spread, so if Verify doesn't find a record yet, give it a few minutes; the dashboard also re-checks on its own. The full walkthrough, including root domains and Cloudflare, is in How to connect your domain to your app.

Once your domain is live, update anything that stores your app’s address, such as AUTH_URL, OAuth callback URLs in Google or GitHub, and NEXT_PUBLIC_ variables that point at your own site.

After launch

  • Every push deploys. Push to your branch and a new deployment starts on its own. Your current version keeps serving visitors until the new one passes its health check, so updates don't cause downtime. You can also deploy by hand from the Deployments tab.
  • Roll back in seconds. If a release misbehaves, open Deployments and click Rollback to this version on an earlier successful deployment. Nothing is rebuilt; the previous version simply comes back.
  • Watch it run. The Logs tab streams your app's output live, with secrets hidden. The Overview shows your app's status and address, with Redeploy, Restart and Stop buttons.

Your app runs as two copies

Every plan runs two copies of your app, so a crash or an update never takes it offline. That has one consequence worth designing for: anything an app keeps only in its own memory or on its own disk isn't shared with the other copy, and it's gone after a redeploy.

Store uploads in object storage rather than writing them into public/ while the app runs, keep sessions in a database or signed cookies, and expect each copy to keep its own cache of regenerated pages.

Common problems

The build runs out of memory

Large Next.js apps can need more memory to build than Node.js allows itself by default. Raise the limit for the build only, in the build script:

"build": "NODE_OPTIONS=--max-old-space-size=1536 next build"

Don’t set NODE_OPTIONS on the Environment tab for this: it would also apply to the running app, which has less memory than the build.

The deployment fails because the app “did not become healthy”

Usually the health check path redirects to a login page or errors without a database. Point it at a route that always answers, such as the /api/health route above, and check the Logs tab for errors while the app starts.

A NEXT_PUBLIC_ variable is undefined in the browser

It was missing or different when the app was built. Set it, then redeploy. With your own Dockerfile, also check it has an ARG line.

“next start” fails with an error about output: export

Your app is a static export. Serve the out folder as described in the static export note above.

For anything else, Deployment Failed? How to Find and Fix the Cause explains how to read a failed deployment.

Keep reading

Ready to deploy?

Start on the Free plan: your first month is on us, and every app runs two copies for high availability.

View App Hosting plans