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.
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
buildandstartscripts inpackage.json(projects made withcreate-next-appalready 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:
22Your scripts should look like this:
{
"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/healthinstead:
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_KEYorAUTH_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 asNEXT_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.comVariables 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:
{
"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:
module.exports = {
output: 'standalone',
};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:
- Enter your domain, for example
www.example.com, and click Add domain. - 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. - 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.
Learning path
Deploy your first app
- 1
Deploy your app
- 2
- 3
- 4
Keep reading
How to Connect Your Domain to Your App (with Free HTTPS)
Put your app on your own domain in three DNS steps. Covers www versus root domains, what each DNS record does, Cloudflare, and what to check when a domain will not verify.
How to Add a Database to Your App
Create a managed PostgreSQL or MariaDB database next to your app and have its connection details handed to your app automatically, with examples for Node.js, Python and Django.
How to Deploy a React App
Take a React app built with Vite or Create React App from your GitHub repository to a live HTTPS address, with environment variables, your own domain and automatic deploys on every push.
Deployment Failed? How to Find and Fix the Cause
Every failed deployment tells you which stage broke and why. How to read it, and the fixes for the failures we see most: missing start commands, port mix-ups, health checks and memory.
Ready to deploy?
Start on the Free plan: your first month is on us, and every app runs two copies for high availability.