DeveloperBootstrap

JavaScript

Next.js 14 Enterprise Guide

Master Next.js 14 enterprise development with App Router, Server Components, TypeScript, authentication, database integration, and production deployment.

What you'll learn

  • Structure an enterprise Next.js monorepo
  • Use Server Components and Client Components correctly
  • Implement JWT authentication with HttpOnly cookies
  • Integrate Prisma ORM with PostgreSQL
  • Build type-safe API routes with Zod validation
  • Deploy to Vercel or a self-hosted VPS
  • Optimise images, fonts, and Core Web Vitals

Project Setup

Bash
1# Create project with all recommended options
2pnpm create next-app@latest my-enterprise-app \
3  --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
4
5cd my-enterprise-app
6
7# Add key dependencies
8pnpm add zod @prisma/client
9pnpm add -D prisma @types/node
10
11# Initialise Prisma
12pnpm prisma init

App Router Architecture

Plain Text
1src/app/
2├── (auth)/              # Route group — no URL segment
3│   ├── login/page.tsx
4│   └── register/page.tsx
5├── (dashboard)/
6│   ├── layout.tsx       # Shared dashboard shell (authenticated)
7│   ├── dashboard/page.tsx
8│   └── settings/page.tsx
9├── api/
10│   └── users/
11│       └── route.ts     # GET /api/users, POST /api/users
12├── layout.tsx           # Root layout — HTML, body, providers
13├── page.tsx             # Home page /
14├── error.tsx            # Error boundary
15├── loading.tsx          # Loading skeleton
16└── not-found.tsx

Server vs Client Components

React TSX
1// Server Component (default) — runs only on server
2// app/dashboard/page.tsx
3import { prisma } from '@/lib/prisma';
4
5export default async function DashboardPage() {
6  // Direct DB access — no fetch round-trip
7  const courses = await prisma.course.findMany({ take: 10 });
8  return <CourseList courses={courses} />;
9}
10
11// Client Component — runs in browser
12// components/course-list.tsx
13'use client';
14import { useState } from 'react';
15
16export function CourseList({ courses }: { courses: Course[] }) {
17  const [search, setSearch] = useState('');
18  const filtered = courses.filter(c => c.title.includes(search));
19  return (
20    <>
21      <input value={search} onChange={e => setSearch(e.target.value)} />
22      {filtered.map(c => <div key={c.id}>{c.title}</div>)}
23    </>
24  );
25}

Authentication

TypeScript
1// lib/auth.ts — JWT in HttpOnly cookie
2import { cookies } from 'next/headers';
3import { jwtVerify } from 'jose';
4
5const SECRET = new TextEncoder().encode(process.env['JWT_SECRET']);
6
7export async function getSession() {
8  const token = (await cookies()).get('access_token')?.value;
9  if (!token) return null;
10  try {
11    const { payload } = await jwtVerify(token, SECRET);
12    return payload as SessionPayload;
13  } catch {
14    return null;
15  }
16}
17
18// middleware.ts — protect routes
19import { NextRequest, NextResponse } from 'next/server';
20
21export function middleware(req: NextRequest) {
22  const token = req.cookies.get('access_token');
23  if (!token && req.nextUrl.pathname.startsWith('/dashboard')) {
24    return NextResponse.redirect(new URL('/login', req.url));
25  }
26  return NextResponse.next();
27}
28
29export const config = { matcher: ['/dashboard/:path*', '/settings/:path*'] };

Database Integration

TypeScript
1// lib/prisma.ts — singleton for Next.js dev hot reload
2import { PrismaClient } from '@prisma/client';
3
4const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
5
6export const prisma =
7  globalForPrisma.prisma ??
8  new PrismaClient({ log: process.env['NODE_ENV'] === 'development' ? ['query'] : [] });
9
10if (process.env['NODE_ENV'] !== 'production') globalForPrisma.prisma = prisma;

API Routes & Validation

TypeScript
1// app/api/users/route.ts
2import { NextResponse } from 'next/server';
3import { z } from 'zod';
4import { prisma } from '@/lib/prisma';
5
6const createUserSchema = z.object({
7  name:  z.string().min(2).max(100),
8  email: z.string().email(),
9});
10
11export async function POST(req: Request) {
12  const body: unknown = await req.json();
13  const result = createUserSchema.safeParse(body);
14
15  if (!result.success) {
16    return NextResponse.json(
17      { error: 'Validation failed', issues: result.error.flatten() },
18      { status: 400 }
19    );
20  }
21
22  const user = await prisma.user.create({ data: result.data });
23  return NextResponse.json(user, { status: 201 });
24}

Production Deployment

Bash
1# Vercel deployment (zero-config)
2pnpm i -g vercel
3vercel --prod
4
5# Self-hosted with Docker
6# next.config.js
7output: 'standalone'
8
9# Dockerfile
10FROM node:20-alpine AS builder
11WORKDIR /app
12COPY . .
13RUN pnpm install --frozen-lockfile && pnpm build
14
15FROM node:20-alpine AS runner
16WORKDIR /app
17COPY --from=builder /app/.next/standalone ./
18COPY --from=builder /app/.next/static ./.next/static
19COPY --from=builder /app/public ./public
20ENV NODE_ENV=production PORT=3000
21EXPOSE 3000
22CMD ["node", "server.js"]