Introduction
Building enterprise-grade applications with Next.js requires careful consideration of architecture, performance, security, and maintainability. This guide provides a comprehensive overview of best practices for developing robust Next.js applications that can scale to meet enterprise demands.1. Architecture and Scalability
Modular Architecture
Organize your Next.js application using a modular architecture to improve maintainability and scalability:Copy
/src
/app # App Router pages and layouts
/components # Reusable UI components
/ui # Base UI components
/features # Feature-specific components
/lib # Utility functions and shared code
/hooks # Custom React hooks
/services # External service integrations
/types # TypeScript type definitions
/styles # Global styles and theme configuration
/middleware # Next.js middleware
Code Splitting and Lazy Loading
Next.js automatically code-splits by route, but you can further optimize with dynamic imports:Copy
// Dynamic import with loading state
import dynamic from 'next/dynamic';
const DynamicDashboard = dynamic(() => import('@/components/Dashboard'), {
loading: () => <p>Loading dashboard...</p>,
ssr: false // Disable SSR if component relies on browser APIs
});
export default function Page() {
return <DynamicDashboard />;
}
Efficient Data Fetching
Leverage React Server Components for efficient data fetching:Copy
// app/products/page.tsx
import { ProductList } from '@/components/ProductList';
import { getProducts } from '@/lib/products';
export default async function ProductsPage() {
// This runs on the server and doesn't send unnecessary data to the client
const products = await getProducts();
return <ProductList products={products} />;
}
Copy
'use client';
import { useQuery } from '@tanstack/react-query';
function ProductsClient() {
const { data, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: () => fetch('/api/products').then(res => res.json())
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading products</div>;
return (
<ul>
{data.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
Deployment Models
Serverless Deployment
Serverless deployments (like Vercel) offer automatic scaling and reduced operational overhead:Copy
// next.config.js for optimized serverless deployment
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone', // Creates a standalone build optimized for containerized environments
}
module.exports = nextConfig;
Containerized Deployment
For Kubernetes or Docker deployments, use the standalone output mode:Copy
# Dockerfile for Next.js
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Create a non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy standalone build
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
Database and Caching Strategies
Database Selection
Choose the right database based on your needs:- PostgreSQL: For complex relational data with ACID compliance
- MongoDB: For flexible schema and document-oriented data
- Redis: For caching and real-time features
- Supabase/Firebase: For rapid development with built-in authentication
Caching Implementation
Implement multi-level caching:Copy
// Route segment caching
export const revalidate = 3600; // Revalidate every hour
// Data cache with fetch
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // Cache for 1 hour
});
return res.json();
}
// Request memoization
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const user = await db.user.findUnique({ where: { id } });
return user;
});
2. Performance Optimization
Image Optimization
Use the Next.js Image component for automatic optimization:Copy
import Image from 'next/image';
export default function ProductImage({ product }) {
return (
<div className="relative h-64 w-full">
<Image
src={product.imageUrl || "/placeholder.svg"}
alt={product.name}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority={product.featured}
className="object-cover rounded-lg"
/>
</div>
);
}
Font Optimization
Optimize fonts using Next.js built-in font system:Copy
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}
Rendering Strategies
Choose the appropriate rendering strategy based on your content:Copy
// Static page with generated static params
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({
id: product.id,
}));
}
// Dynamic page with ISR
export const revalidate = 60; // Revalidate at most once per minute
// Dynamic page with on-demand revalidation
// pages/api/revalidate.js
export default async function handler(req, res) {
if (req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
await res.revalidate('/products');
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
}
Performance Monitoring
Implement performance monitoring with tools like Web Vitals:Copy
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitalsReporter() {
useReportWebVitals(metric => {
// Send to analytics
console.log(metric);
// Example: send to Google Analytics
const analyticsId = 'UA-XXXXX-Y';
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
});
navigator.sendBeacon(`https://www.google-analytics.com/collect?v=1&t=event&ec=Web%20Vitals&ea=${metric.name}&el=${metric.id}&ev=${metric.value}&tid=${analyticsId}`, body);
});
return null;
}
3. Security Best Practices
Input Validation
Always validate user input on both client and server:Copy
// Server-side validation in a Server Action
'use server';
import { z } from 'zod';
const FormSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
export async function createUser(prevState: any, formData: FormData) {
const validatedFields = FormSchema.safeParse({
email: formData.get('email'),
password: formData.get('password'),
});
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to create user.',
};
}
// Proceed with creating user...
}
API Security
Secure your API routes with proper authentication and rate limiting:Copy
// app/api/protected/route.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { NextResponse } from 'next/server';
import { rateLimit } from '@/lib/rate-limit';
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Apply rate limiting
const limiter = rateLimit({
interval: 60 * 1000, // 1 minute
uniqueTokenPerInterval: 500,
});
try {
await limiter.check(10, session.user.id); // 10 requests per minute per user
} catch {
return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 });
}
// Process the request
return NextResponse.json({ data: 'Protected data' });
}
Authentication and Authorization
Implement robust authentication with NextAuth.js:Copy
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { compare } from 'bcrypt';
import { prisma } from '@/lib/prisma';
export const authOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { email: credentials.email }
});
if (!user) {
return null;
}
const isPasswordValid = await compare(credentials.password, user.password);
if (!isPasswordValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
};
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
}
return token;
},
async session({ session, token }) {
session.user.role = token.role;
return session;
}
},
pages: {
signIn: '/auth/signin',
error: '/auth/error',
},
session: {
strategy: 'jwt',
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
CSRF Protection
Implement CSRF protection for forms:Copy
'use client';
import { useState } from 'react';
import { getCsrfToken } from 'next-auth/react';
export default function ContactForm() {
const [csrfToken, setCsrfToken] = useState('');
useEffect(() => {
async function fetchCsrfToken() {
const token = await getCsrfToken();
setCsrfToken(token || '');
}
fetchCsrfToken();
}, []);
return (
<form method="post" action="/api/contact">
<input name="csrfToken" type="hidden" value={csrfToken} />
{/* Form fields */}
</form>
);
}
Content Security Policy
Implement a strict Content Security Policy:Copy
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Add security headers
response.headers.set('Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://analytics.example.com; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https://images.example.com; " +
"font-src 'self'; " +
"connect-src 'self' https://api.example.com; " +
"frame-src 'none'; " +
"object-src 'none';"
);
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
return response;
}
export const config = {
matcher: '/((?!api/auth|_next/static|_next/image|favicon.ico).*)',
};
4. Code Quality and Maintainability
TypeScript Integration
Use TypeScript for type safety and better developer experience:Copy
// types/index.ts
export interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
}
export interface Product {
id: string;
name: string;
description: string;
price: number;
imageUrl: string;
category: string;
featured: boolean;
}
Linting and Formatting
Set up ESLint and Prettier for consistent code style:Copy
// .eslintrc.json
{
"extends": [
"next/core-web-vitals",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"plugins": ["@typescript-eslint"],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"react/no-unescaped-entities": "off",
"@typescript-eslint/no-explicit-any": "warn",
"react-hooks/exhaustive-deps": "warn"
}
}
Copy
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100
}
Testing Strategy
Implement a comprehensive testing strategy:Copy
// Unit test with Jest and React Testing Library
// __tests__/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '@/components/ui/Button';
describe('Button component', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
});
it('calls onClick handler when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button', { name: /click me/i }));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Copy
// Integration test with Cypress
// cypress/e2e/authentication.cy.ts
describe('Authentication', () => {
it('should allow a user to sign in', () => {
cy.visit('/auth/signin');
cy.get('input[name="email"]').type('[email protected]');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.contains('Welcome back').should('be.visible');
});
});
Component Documentation
Document your components with Storybook:Copy
// stories/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@/components/ui/Button';
const meta: Meta<typeof Button> = {
title: 'UI/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
children: 'Button',
variant: 'default',
},
};
export const Destructive: Story = {
args: {
children: 'Delete',
variant: 'destructive',
},
};
5. Deployment and DevOps
CI/CD Pipeline
Set up a CI/CD pipeline with GitHub Actions:Copy
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test
build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: build
path: .next
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download build artifact
uses: actions/download-artifact@v3
with:
name: build
path: .next
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Multi-Environment Setup
Configure environment-specific settings:Copy
.env # Default environment variables
.env.local # Local overrides (not committed)
.env.development # Development environment
.env.test # Test environment
.env.production # Production environment
Monitoring and Logging
Implement application monitoring with Sentry:Copy
// app/monitoring.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
environment: process.env.NODE_ENV,
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
],
});
// Instrument Next.js error handling
export function onRequestError({ error, request, context }) {
Sentry.captureException(error, {
extra: {
requestUrl: request.url,
context,
},
});
}
export function register() {
// Initialize any other monitoring tools
}
6. State Management and Data Fetching
Server State Management
Use React Query for server state management:Copy
// lib/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 1,
refetchOnWindowFocus: process.env.NODE_ENV === 'production',
},
},
}));
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Client State Management
Use Zustand for client-side state management:Copy
// store/useCartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
addItem: (item: Omit<CartItem, 'quantity'>) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
totalItems: () => number;
totalPrice: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existingItem = state.items.find((i) => i.id === item.id);
if (existingItem) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (id) => set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
updateQuantity: (id, quantity) => set((state) => ({
items: state.items.map((i) =>
i.id === id ? { ...i, quantity } : i
),
})),
clearCart: () => set({ items: [] }),
totalItems: () => get().items.reduce((acc, item) => acc + item.quantity, 0),
totalPrice: () => get().items.reduce((acc, item) => acc + (item.price * item.quantity), 0),
}),
{
name: 'cart-storage',
}
)
);
Data Fetching Patterns
Implement efficient data fetching patterns:Copy
// Parallel data fetching
async function ParallelDataFetching() {
// Fetch data in parallel
const productsPromise = getProducts();
const categoriesPromise = getCategories();
const featuredPromise = getFeaturedProducts();
// Wait for all promises to resolve
const [products, categories, featured] = await Promise.all([
productsPromise,
categoriesPromise,
featuredPromise,
]);
return (
<div>
<FeaturedProducts products={featured} />
<CategoryNav categories={categories} />
<ProductGrid products={products} />
</div>
);
}
// Sequential data fetching (when one request depends on another)
async function SequentialDataFetching() {
// First fetch categories
const categories = await getCategories();
// Then fetch products for the first category
const products = categories.length > 0
? await getProductsByCategory(categories[0].id)
: [];
return (
<div>
<CategoryNav categories={categories} />
<ProductGrid products={products} />
</div>
);
}
7. Error Handling and Monitoring
Global Error Handling
Implement global error boundaries:Copy
// app/global-error.tsx
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to Sentry
Sentry.captureException(error);
}, [error]);
return (
<html>
<body>
<div className="flex min-h-screen flex-col items-center justify-center p-4 text-center">
<h1 className="text-4xl font-bold mb-4">Something went wrong!</h1>
<p className="mb-8 text-gray-600">
We've been notified about this issue and are working to fix it.
</p>
<button
onClick={() => reset()}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
>
Try again
</button>
</div>
</body>
</html>
);
}
Route-Specific Error Handling
Implement route-specific error boundaries:Copy
// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
import { Button } from '@/components/ui/button';
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to Sentry
Sentry.captureException(error);
}, [error]);
return (
<div className="p-8 text-center">
<h2 className="text-2xl font-bold mb-4">Dashboard Error</h2>
<p className="mb-4 text-gray-600">
There was an error loading the dashboard.
</p>
<div className="flex justify-center gap-4">
<Button onClick={() => reset()}>Try again</Button>
<Button variant="outline" onClick={() => window.location.href = '/'}>
Go to Home
</Button>
</div>
</div>
);
}
Structured Logging
Implement structured logging:Copy
// lib/logger.ts
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogEntry {
level: LogLevel;
message: string;
timestamp: string;
context?: Record<string, any>;
}
class Logger {
private context: Record<string, any> = {};
constructor(context: Record<string, any> = {}) {
this.context = context;
}
private log(level: LogLevel, message: string, additionalContext: Record<string, any> = {}) {
const timestamp = new Date().toISOString();
const entry: LogEntry = {
level,
message,
timestamp,
context: {
...this.context,
...additionalContext,
},
};
// In development, log to console
if (process.env.NODE_ENV === 'development') {
console[level === 'debug' ? 'log' : level](JSON.stringify(entry, null, 2));
} else {
// In production, send to logging service
// Example: send to Datadog, Loggly, etc.
// For now, still log to console in production
console[level === 'debug' ? 'log' : level](JSON.stringify(entry));
}
return entry;
}
debug(message: string, context?: Record<string, any>) {
return this.log('debug', message, context);
}
info(message: string, context?: Record<string, any>) {
return this.log('info', message, context);
}
warn(message: string, context?: Record<string, any>) {
return this.log('warn', message, context);
}
error(message: string, error?: Error, context?: Record<string, any>) {
return this.log('error', message, {
...context,
error: error ? {
message: error.message,
stack: error.stack,
name: error.name,
} : undefined,
});
}
withContext(additionalContext: Record<string, any>) {
return new Logger({
...this.context,
...additionalContext,
});
}
}
export const logger = new Logger({
service: 'next-app',
environment: process.env.NODE_ENV,
});
8. Accessibility and Internationalization
Accessibility Implementation
Ensure your application is accessible:Copy
// components/ui/Button.tsx
import { forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'underline-offset-4 hover:underline text-primary',
},
size: {
default: 'h-10 py-2 px-4',
sm: 'h-9 px-3 rounded-md',
lg: 'h-11 px-8 rounded-md',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };
Implementing ARIA Attributes
Copy
// components/ui/Dialog.tsx
'use client';
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { cn } from '@/lib/utils';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
aria-label="Close dialog"
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
export { Dialog, DialogTrigger, DialogContent, DialogClose };
Internationalization
Implement internationalization with next-intl:Copy
// middleware.ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// A list of all locales that are supported
locales: ['en', 'es', 'fr', 'de'],
// If this locale is matched, pathnames work without a prefix (e.g. `/about`)
defaultLocale: 'en',
// Domains can be used to match specific locales
domains: [
{
domain: 'example.com',
defaultLocale: 'en'
},
{
domain: 'example.es',
defaultLocale: 'es'
}
]
});
export const config = {
// Skip all paths that should not be internationalized
matcher: ['/((?!api|_next|.*\\..*).*)']
};
Copy
// messages/en.json
{
"Index": {
"title": "Hello world!",
"description": "This is a sample application"
},
"Navigation": {
"home": "Home",
"about": "About",
"products": "Products",
"contact": "Contact"
},
"Auth": {
"signIn": "Sign In",
"signUp": "Sign Up",
"email": "Email",
"password": "Password",
"forgotPassword": "Forgot Password?"
}
}
Copy
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation';
export function generateStaticParams() {
return [{ locale: 'en' }, { locale: 'es' }, { locale: 'fr' }, { locale: 'de' }];
}
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
let messages;
try {
messages = (await import(`../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}
return (
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
);
}
Copy
// app/[locale]/page.tsx
import { useTranslations } from 'next-intl';
export default function Index() {
const t = useTranslations('Index');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
9. Team Collaboration and Development Workflow
Git Workflow
Implement a robust Git workflow:Copy
# Feature branch workflow
git checkout -b feature/user-authentication
# Make changes
git add .
git commit -m "feat: implement user authentication"
# Push to remote
git push -u origin feature/user-authentication
# Create pull request
# After review and approval, merge to main
Conventional Commits
Use conventional commits for better changelog generation:Copy
feat: add user authentication
fix: resolve issue with password reset
docs: update README with setup instructions
style: format code according to style guide
refactor: simplify product filtering logic
test: add tests for cart functionality
chore: update dependencies
Code Review Guidelines
Establish code review guidelines:- Functionality: Does the code work as expected?
- Security: Are there any security vulnerabilities?
- Performance: Is the code optimized for performance?
- Maintainability: Is the code easy to understand and maintain?
- Testing: Are there sufficient tests?
- Documentation: Is the code well-documented?
Dependency Management
Manage dependencies effectively:Copy
// package.json
{
"name": "enterprise-nextjs-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch",
"e2e": "cypress run",
"e2e:open": "cypress open",
"format": "prettier --write .",
"prepare": "husky install",
"check-deps": "npx npm-check-updates"
},
"dependencies": {
"@sentry/nextjs": "^7.64.0",
"next": "^15.0.0",
"next-auth": "^4.24.5",
"next-intl": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.22.2",
"zustand": "^4.4.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.4",
"@types/node": "^20.6.0",
"@types/react": "^18.2.21",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"cypress": "^13.1.0",
"eslint": "^8.49.0",
"eslint-config-next": "^15.0.0",
"eslint-config-prettier": "^9.0.0",
"husky": "^8.0.3",
"jest": "^29.6.4",
"jest-environment-jsdom": "^29.6.4",
"lint-staged": "^14.0.1",
"prettier": "^3.0.3",
"typescript": "^5.2.2"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,md}": [
"prettier --write"
]
}
}
10. Advanced Topics
Monorepo Setup with Turborepo
Set up a monorepo with Turborepo:Copy
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
}
}
}
Copy
/apps
/web # Main Next.js application
/admin # Admin dashboard
/docs # Documentation site
/packages
/ui # Shared UI components
/utils # Shared utilities
/api-client # API client library
/config # Shared configuration
/tsconfig # Shared TypeScript configuration
Micro-Frontends
Implement micro-frontends with Module Federation:Copy
// next.config.js
const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
module.exports = {
webpack(config, options) {
const { isServer } = options;
config.plugins.push(
new NextFederationPlugin({
name: 'main',
remotes: {
shop: `shop@${process.env.SHOP_URL}/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
blog: `blog@${process.env.BLOG_URL}/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
},
filename: 'static/chunks/remoteEntry.js',
exposes: {
'./Header': './components/Header',
'./Footer': './components/Footer',
'./AuthContext': './contexts/AuthContext',
},
shared: {
react: {
singleton: true,
requiredVersion: false,
},
'react-dom': {
singleton: true,
requiredVersion: false,
},
},
})
);
return config;
},
};
Edge Computing
Leverage Edge Computing for global performance:Copy
// app/api/geo/route.ts
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const ip = request.headers.get('x-forwarded-for') || 'unknown';
// Get user's country from request headers
const country = request.headers.get('x-vercel-ip-country') || 'unknown';
const region = request.headers.get('x-vercel-ip-country-region') || 'unknown';
const city = request.headers.get('x-vercel-ip-city') || 'unknown';
return Response.json({
ip,
geo: {
country,
region,
city,
},
timestamp: new Date().toISOString(),
});
}
Serverless Functions
Implement serverless functions for specific tasks:Copy
// app/api/generate-thumbnail/route.ts
import { put } from '@vercel/blob';
import { NextResponse } from 'next/server';
import sharp from 'sharp';
export async function POST(request: Request) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json(
{ error: 'No file provided' },
{ status: 400 }
);
}
// Convert file to buffer
const buffer = Buffer.from(await file.arrayBuffer());
// Generate thumbnail
const thumbnail = await sharp(buffer)
.resize(200, 200, { fit: 'inside' })
.toBuffer();
// Upload to Vercel Blob
const { url } = await put(`thumbnails/${Date.now()}-${file.name}`, thumbnail, {
access: 'public',
contentType: 'image/jpeg',
});
return NextResponse.json({ url });
} catch (error) {
console.error('Error generating thumbnail:', error);
return NextResponse.json(
{ error: 'Failed to generate thumbnail' },
{ status: 500 }
);
}
}