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 initApp 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.tsxServer 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"]