Most Next.js guides steer you toward server-side rendering and a Node process that never sleeps. Static export flips that: your app builds to plain HTML, CSS, and JavaScript at deploy time, then gets served from a CDN with no container running. Zero idle cost, instant global delivery, and the build step is the only thing that consumes resources.
The catch is port binding and API routes don't exist in static mode — if your app tries to listen on a port or run getServerSideProps, the build fails. You trade dynamic rendering for speed and cost, which is the right choice for marketing sites, documentation, portfolios, and any app whose data changes on a build schedule rather than per-request.
Why static export breaks the default Next.js deploy path
Next.js defaults to a Node server. When you run next build, it produces a .next directory optimized for next start, which boots an HTTP server. Most platforms detect package.json, see Next.js in dependencies, and run npm run build && npm start — which works for SSR but hangs forever for static exports because there is no server to start.
Static export requires next.config.js with output: 'export', which changes the build artifact to an out/ directory of HTML files. The start command becomes irrelevant; you need a platform that recognizes the static output and serves it from a CDN instead of trying to execute it.
PandaStack auto-detects static frameworks when type is "static" or "auto". It looks for out/, dist/, build/, and other common output directories, deploys the files to CDN storage, purges the edge cache, and returns a live URL. No container runs after the build finishes.
Deploy via the REST API with a GitHub repo
The API is the most direct path when you control the request — CI pipelines, scripts, or any automation that already has access to a PandaStack token. Create a psk_live_ token in the dashboard under Settings → API Tokens; the token has your organization baked in, so no x-organization-id header is needed.
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "portfolio-site",
"repositoryName": "yourname/portfolio",
"branch": "main",
"autoDeploy": true,
"buildCommand": "npm run build",
"outputDir": "out"
}'Response:
{
"success": true,
"data": {
"projectId": 142,
"name": "portfolio-site",
"deploymentId": 890,
"deploymentUuid": "abc123-def456"
}
}The build starts immediately. You can stream logs via GET /v1/projects/{projectId}/deployments/{deploymentId}/logs or watch in the dashboard. When the status reaches RUNNING, the site is live at https://portfolio-site-abc123.pandastack.app.
If the build fails with "Could not find a production build in the 'out' directory," Next.js is not configured for static export. Add output: 'export' to next.config.js and push the commit; the next deploy will succeed.
Add the deploy button to your README for one-click deploys
README badges turn a visitor into a deployed instance with one click. The user lands on a pre-filled deploy screen, confirms the settings, and the build kicks off — no manual API token or CLI install required.
[](https://dashboard.pandastack.io/deploy?repo=yourname/portfolio&type=static&buildCmd=npm%20run%20build&outputDir=out)Query parameters on /deploy:
repo(required):owner/repotype:static(forces static detection instead of auto)buildCmd: URL-encoded build commandoutputDir: the directory Next.js writes to (outby default)branch: defaults tomain
When someone clicks the badge, they see the repository URL, build command, and output directory locked in. They can override environment variables if the repo has a pandastack.json file that declares them, but the core settings come from the query string.
This is how starter templates and library examples distribute themselves: the button is the distribution mechanism.
How pandastack.json makes environment prompts work
A static site usually has environment variables baked into the build — API endpoints, analytics IDs, feature flags. If you hard-code them in .env.local and commit that file, the deploy works but anyone who forks your repo gets your values. If you omit .env.local, the build succeeds but the app is broken.
pandastack.json in the repo root solves this: its env array tells the deploy screen to prompt for values before the build starts.
{
"type": "static",
"name": "portfolio-site",
"buildCommand": "npm run build",
"outputDir": "out",
"env": [
{
"key": "NEXT_PUBLIC_API_URL",
"description": "Backend API endpoint (e.g., https://api.example.com)"
},
{
"key": "NEXT_PUBLIC_ANALYTICS_ID",
"description": "Google Analytics measurement ID"
}
]
}When a user deploys via the button or dashboard, the screen shows two input fields with the descriptions as help text. They fill them in, click Deploy, and the build receives those variables. The app boots correctly on the first try instead of requiring a redeploy after discovering missing config.
This is the Vercel-parity behavior and the cleanest onboarding path for open-source templates.
Why the site loads instantly but a container app cold-starts
Static sites are files in CDN storage. When a request arrives, the edge node serves the HTML from cache — there is no container to wake up, no health check to pass, no process to fork. The first request and the millionth request have the same latency because nothing is executing.
Container apps on the free tier scale to zero after inactivity. The first request after a quiet period hits the orchestrator, which schedules a pod, pulls the image, starts the process, waits for the health check to pass, then proxies the request. That sequence takes a few seconds. Static sites skip all of it.
The trade-off is that static sites cannot handle POST requests, run server-side logic, or connect to a database at request time. If your app needs any of those, it is a container app and will cold-start. If it is read-only content rendered at build time, static is faster and cheaper.
Set a custom domain and automatic SSL
The auto-generated subdomain works for testing, but production sites need a real domain. In the project settings, add the custom domain, then point a CNAME record at the target PandaStack provides. SSL certificates provision automatically via Let's Encrypt and renew before expiration.
For apex domains (no www.), you need a DNS provider that supports CNAME flattening or ALIAS records — Cloudflare, DNSimple, and most modern providers do. Legacy providers that require an A record for the apex will not work because the underlying CDN IP addresses can change.
Once the DNS resolves, the site is live at your domain. The old subdomain continues to work as an alias, which is useful for preview links and rollback testing.
Redeploy on content changes with autoDeploy
Static sites are only as fresh as the last build. If your content comes from a CMS, a data file in the repo, or an external API that you fetch at build time, changes do not appear until you redeploy.
Setting "autoDeploy": true in the API request or enabling it in the dashboard triggers a new build every time the branch receives a push. If your workflow is "edit markdown in GitHub, commit, wait 60 seconds, see the live site update," this works perfectly.
For content that changes more often than you want to rebuild, you need client-side fetching or a container app with server-side rendering. Static sites are snapshot delivery; the snapshot updates on every deploy.
References
- [Next.js static exports](https://nextjs.org/docs/app/building-your-application/deploying/static-exports)
- [PandaStack static site docs](https://docs.pandastack.io/projects/static/)
- [Deploy button parameters](https://docs.pandastack.io/projects/deploy-button/)