Vercel

Deploying Next.js Apps

This lesson teaches the deployment mental model for a beginner who has already deployed a static site and a Vite frontend. A Next.js app can be a static site, a server-rendered app, an API surface, or a mix of all three. The important skill is knowing which parts run at build time, which parts run on the server during a request, which parts run in the browser, and which files are public.

Why Next.js And Vercel Fit Together

Vercel was built by the company that also maintains Next.js, so the platform has framework-aware behavior for many Next.js features. Vercel can detect a Next.js project, use the correct build flow, understand framework output, and deploy server-rendered routes through Vercel Functions. You usually do not need to manually configure an output directory the way you do with a plain static app.

That convenience is useful, but it can hide complexity. A Next.js deployment is not one thing. A single app might include static marketing pages, server-rendered account pages, API routes, optimized images, cached data, and client-side interactivity. Those pieces have different failure modes and different cost profiles.

A practical Next.js deployment review asks:

Which routes are static?
Which routes render on the server?
Which routes expose APIs?
Which components run in the browser?
Which environment variables are public?
Which files are served directly from public/?
Which features may create function, image, bandwidth, or analytics usage?

If you can answer those questions, Vercel feels predictable instead of magical.

Basic Project Settings

A normal Next.js project has scripts similar to these:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

For Vercel, the important script is usually next build. Vercel detects the framework and handles the serving behavior for the deployed output. You should not usually set the output directory to dist or build for a standard Next.js app. Those names come from other tools.

Keep the first deployment simple. Import the repository, confirm the framework preset is Next.js, set Environment Variables if the app needs them, and deploy. Avoid adding a custom vercel.json file unless you have a specific reason. Next.js already has its own routing, redirects, rewrites, headers, and configuration options, and mixing framework configuration with platform configuration without a reason can make debugging harder.

Static Rendering

Some Next.js routes can be generated as static files. Static pages are excellent for content that does not need per-request personalization: marketing pages, documentation, public blog posts, pricing pages, help articles, and simple landing pages. Static rendering is fast because the page can be served from the edge cache or static output rather than computed for every request.

Static does not mean unprofessional or toy. A commercial site may have many static pages. The useful question is whether the page content can be safely prepared ahead of time. If the page shows public content that changes only when the site rebuilds, static rendering may be ideal.

The review point is freshness. If a product price, legal notice, inventory status, or customer-specific message must be current at request time, do not assume a static page is correct. Use the rendering mode that matches the data requirement.

Server-Side Rendering

Server-Side Rendering means a route is rendered dynamically on the server when a request arrives. On Vercel, server-rendered Next.js routes can run through Vercel Functions. That is useful for authenticated pages, request-specific data, cookies, geolocation-aware behavior, dashboards, and pages that need private server-side credentials.

SSR is powerful because the browser receives rendered HTML while private work can stay on the server. It also has operational tradeoffs. Server-rendered routes can use function execution time, can fail because of backend dependencies, and need runtime logs when debugging. A slow database query or an unreliable third-party API can turn into a slow page.

Do not move every page to SSR because it feels more dynamic. Use SSR when request-time behavior is needed. Use static rendering when it is enough. Use caching or revalidation patterns where they match the product requirement.

Route Handlers And API Routes

In the App Router, a route.ts or route.js file can expose an API endpoint. That endpoint is public when it is part of a public route path. It can receive HTTP requests, validate input, call private services, and return a response.

A small conceptual Route Handler might look like this:

export async function POST(request: Request) {
  const body = await request.json();

  if (typeof body.email !== 'string') {
    return Response.json({ error: 'Email is required' }, { status: 400 });
  }

  return Response.json({ ok: true });
}

This example intentionally avoids real provider credentials. In a production app, the handler might use an email service, payment API, CRM, or database. Those secrets belong on the server side, not in Client Components or public JavaScript.

Route Handlers should be treated as backend code. Validate input. Return appropriate status codes. Avoid logging secrets. Test failure cases. Watch Runtime Logs after deployment.

App Router Basics

The App Router uses folders to define routes. A folder becomes a public page when it contains a page file. A folder can expose an API endpoint when it contains a route file. Layout files wrap route segments. Loading and error files define route-specific loading and error UI.

A small mental model looks like this:

app/page.tsx -> /
app/about/page.tsx -> /about
app/blog/[slug]/page.tsx -> /blog/:slug
app/api/contact/route.ts -> /api/contact

Not every file in the app directory becomes a URL. Next.js route conventions decide what is routable. This is different from public/, where files are served directly by path. Keep that distinction clear when thinking about private code and public assets.

Server Components And Client Components

In the App Router, pages and layouts are Server Components by default. Server Components are a good place to fetch server-side data, use private tokens, reduce browser JavaScript, and render content before it reaches the client. Client Components are for interactivity, event handlers, browser APIs, state, effects, and custom hooks that depend on the browser.

A Client Component starts with the directive:

'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

The mistake is adding 'use client' too broadly. Once a file is a Client Component boundary, its imports and children can become part of the client bundle. That can increase JavaScript sent to the browser and can accidentally pull code toward the browser that should have stayed server-only.

Keep interactive components small and specific. Put private data access in Server Components, Route Handlers, or other server-only modules. Pass only safe, serializable data into Client Components.

Public Assets

Next.js serves files from the root public directory directly. For example, public/logo.png is available at /logo.png, and public/avatars/me.png is available at /avatars/me.png. This is convenient for favicons, robots files, small static downloads, images, and other assets that are meant to be public.

It is not a private storage area. Never put secrets, private documents, database exports, certificates, private keys, paid downloadable products, or customer uploads in public/. If a file is in public/, design as though anyone can request it.

For user uploads, use a storage service designed for uploads and access control. Later in the track, you will look at Vercel Blob, Edge Config, databases, and external storage providers.

Image Optimization

Next.js includes the next/image component, and Vercel supports image optimization for deployed Next.js apps. This can improve performance by resizing and serving images more efficiently, but it is still a platform feature with usage and cost considerations. Large images, many remote images, and high traffic can matter.

Use image optimization deliberately. Set meaningful alt text. Provide width and height where required. Configure allowed remote image sources when the app loads images from another domain. Do not use optimization as an excuse to upload huge originals without review.

A production image review asks whether the image is necessary, whether the original is reasonably sized, whether layout shift is avoided, and whether the plan's usage limits make sense for the expected traffic.

Environment Variables

Next.js has a public/private environment variable boundary. Variables intended for browser code commonly use the NEXT_PUBLIC_ prefix. Values without that prefix should stay server-side when used from server-only code. The exact safety still depends on where you import and execute the code, so do not rely on naming alone to fix a poor architecture.

Use public variables for values that are safe to reveal, such as a public analytics id or public API base URL. Use private variables for database URLs, API secrets, email tokens, webhook secrets, and payment secret keys. Store those private values in Vercel Environment Variables, scoped appropriately for Local, Preview, and Production.

After changing build-time variables, redeploy the app. Browser bundles and prebuilt output do not automatically rewrite themselves because a dashboard value changed.

Common Deployment Problems

The first common problem is treating a Next.js app like a Vite app and setting the wrong output directory. Let the Next.js preset do its job unless the project has a documented custom output mode.

The second problem is using browser-only APIs in Server Components. If a component uses window, localStorage, event handlers, or React state, it likely needs to be a Client Component or must be refactored.

The third problem is exposing secrets through NEXT_PUBLIC_ variables or by importing server-only code into client code.

The fourth problem is assuming every route is static. Check whether the page depends on cookies, headers, request-time auth, or uncached data.

The fifth problem is ignoring Runtime Logs. Build Logs tell you why deployment failed. Runtime Logs tell you why a server-rendered route or API endpoint failed after deployment.

Production Checks

For a beginner project, a good Next.js Preview Deployment checklist is concrete:

Homepage loads.
Static pages load.
Dynamic routes load with valid and invalid params.
Authenticated or personalized routes behave correctly.
Route Handlers return expected status codes.
Client Components hydrate without console errors.
Images load and do not cause obvious layout shift.
Public files are intentionally public.
Preview variables point to staging services.
Production variables point to production services.
Runtime Logs are clean after smoke testing.

For commercial, client, business, paid, ad-supported, or production business projects, also review the Vercel plan, team ownership, deployment protection, usage limits, and billing responsibility. Next.js can make a sophisticated full-stack deployment feel simple, but simple deployment does not remove operational responsibility.

Review Questions

  • Why does Vercel work especially well with Next.js projects?
  • What is the difference between a static route and a server-rendered route?
  • When should you use a Route Handler?
  • Why should 'use client' be applied narrowly?
  • What is public about the public/ directory?
  • Why should Runtime Logs be checked after testing server-rendered routes?

Official References