Week 11

App Router

Data Fetching and Mutations

Migrating from React Router

Going to production

Rendering Strategies

Practice

Assignment

Front end Track

Next.js Production Build

When you build a normal React app (npm run build), you get a dist/ folder: static HTML, JS, and CSS files that any web server or CDN can serve. There's no server involved.

Next.js is different. Run npm run build and you get a .next/ folder that contains:

This means you can't just upload a Next.js build to a static host like GitHub Pages. You need a host that understands Next.js - Vercel being the most common choice (Vercel is the company that builds Next.js).

# React(Vite) build output
dist/
├── index.html
├── assets/
│   ├── index-abc123.js
│   └── index-abc123.css

# Next.js build output (.next/)
.next/
├── static/          ← static assets, CSS, JS chunks
├── server/          ← server-rendered pages and routes
├── cache/           ← build cache
└── BUILD_ID         ← unique ID for this build

You can export a fully static Next.js site with output: 'export' in next.config.ts if your app has no server-side features. But as soon as you use Server Functions, server-side rendering, or API routes, you need a proper server.

<aside> ⌨️

Hands on: Run npm run build in your project. Read the output carefully - find the table showing static (○) and dynamic (λ) routes. If a route you expected to be static is showing as dynamic, check whether it uses cache: 'no-store' or cookies()/headers() - those force dynamic rendering. Also find the "First Load JS" column: pages with a large bundle are shipping a lot of JavaScript to the browser.

</aside>

Environment Variables

Next.js distinguishes between server-side and client-side environment variables. This matters for security: you don't want API keys ending up in the JavaScript bundle that ships to every browser.

Server-only variables (the default)

These are available only during server-side rendering and in Server Functions. They're never included in the browser bundle.

# .env.local
DATABASE_URL=postgresql://...
STRIPE_SECRET_KEY=xx_...
API_KEY=xx...
// app/checkout/actions.ts - runs on the server, safe
"use server"

export async function createPaymentIntent() {
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!) // ✅ server-only
}

Client-accessible variables: NEXT_PUBLIC_

Prefix a variable with NEXT_PUBLIC_ to include it in the browser bundle. Only do this for values that are safe to expose publicly (analytics IDs, public API URLs, feature flags).

# .env.local
NEXT_PUBLIC_ANALYTICS_ID=G-XXXXXXXXXX
NEXT_PUBLIC_API_URL=https://api.example.com
// components/Analytics.tsx - runs in the browser
"use client"

console.log(process.env.NEXT_PUBLIC_ANALYTICS_ID) // ✅ available
console.log(process.env.DATABASE_URL)              // ❌ undefined - stripped from bundle

<aside> ⚠️

If you put a secret key in NEXT_PUBLIC_, it will be visible in the JavaScript source that any user can inspect. Never prefix secrets with NEXT_PUBLIC_.

</aside>

.env file precedence

Next.js loads environment files in this order (later overrides earlier):

  1. .env - defaults committed to source control
  2. .env.local - local overrides, never commit this file (add to .gitignore)
  3. .env.production / .env.development - environment-specific

<aside> ⌨️

Hands on: Create a .env.local file in your project with two variables: MY_SECRET=server-only and NEXT_PUBLIC_APP_NAME=My Portfolio. Add console.log(process.env.MY_SECRET) to a Server Component and console.log(process.env.NEXT_PUBLIC_APP_NAME) to a Client Component ("use client"). Run the dev server and check where each log appears. Then look at the built JavaScript bundle - open DevTools, search for the variable values to confirm MY_SECRET is not there but NEXT_PUBLIC_APP_NAME is. Don’t forget to add .env.local to .gitignore.

</aside>

Deploying to Vercel

First deployment

  1. Push your Next.js project to a GitHub repository
  2. Go to vercel.com, sign in with GitHub
  3. Click Add New Project and import your repository
  4. Vercel detects Next.js automatically - no configuration needed
  5. Click Deploy

That's it. Vercel runs npm run build, deploys the output, and gives you a URL.

Adding environment variables

In the Vercel dashboard:

  1. Open your project → SettingsEnvironment Variables
  2. Add each variable (name + value)
  3. Choose which environments it applies to: Production, Preview, Development
  4. Save

<aside> ⚠️

Adding an environment variable in the Vercel dashboard does not automatically apply it to your current deployment. You must trigger a new deployment (redeploy) for the change to take effect.

</aside>

Build output in the Vercel dashboard

After a deploy, Vercel shows you the build output - which routes are static (⚡ fast) and which are dynamic (λ server-rendered). This is useful for spotting accidental dynamic routes.

Route (app)                       Size     First Load JS
┌ ○ /                             1.2 kB        87.5 kB
├ ○ /about                        839 B         87.2 kB
├ λ /products                     1.5 kB        88.1 kB
└ λ /products/[id]                2.1 kB        88.5 kB

○  (Static)   prerendered as static content
λ  (Dynamic)  server-rendered on demand

Redeployment After Environment Variable Changes

To redeploy without a code change: Deployments → find the latest deployment → Redeploy.

Local Build Testing

Always test a production build locally before deploying:

npm run build    # build the .next/ folder
npm run start    # serve it on localhost:3000

This catches issues that only appear in production mode (missing env vars, server-only code accidentally called on the client, etc.).

Additional Resources

Reading


The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

CC BY-NC-SA 4.0 Icons

Built with ❤️ by the HackYourFuture community · Thank you, contributors

Found a mistake or have a suggestion? Let us know in the feedback form.