Set up production-ready Next.js applications with TypeScript, ESLint, Prettier, and modern development tooling for enterprise-grade React development.
Set up production-ready Next.js applications with TypeScript, ESLint, Prettier, and modern development tooling for enterprise-grade React development.
Welcome to the comprehensive Next.js & TypeScript Setup lesson! This lesson will teach you how to create production-ready React applications using the modern development stack that powers the world's most successful web applications.
Setting up a proper development environment is crucial for building maintainable, scalable React applications. This lesson covers everything you need to know about configuring Next.js with TypeScript and essential development tools.
Next.js has become the de facto standard for production React applications because it provides:
TypeScript transforms JavaScript development by providing:
This comprehensive lesson is divided into several hands-on sections:
Setup & Installation (20 minutes)
Development Tools Configuration (25 minutes)
Project Structure & Organization (20 minutes)
Build & Deployment Preparation (20 minutes)
Before we begin, ensure you have:
By the end of this lesson, you'll have:
Let's begin building your production-ready React development environment!
Let's start by creating a new Next.js application with TypeScript support. Next.js provides an excellent starting template that includes TypeScript configuration out of the box.
Use the official create-next-app command with TypeScript template:
1# Using npm
2npx create-next-app@latest my-production-app --typescript --tailwind --eslint --app
3
4# Using yarn
5yarn create next-app my-production-app --typescript --tailwind --eslint --app
6
7# Using pnpm
8pnpm create next-app my-production-app --typescript --tailwind --eslint --appThe generated project includes:
1my-production-app/
2├── app/ # App Router directory (Next.js 13+)
3│ ├── globals.css # Global styles
4│ ├── layout.tsx # Root layout component
5│ └── page.tsx # Home page component
6├── public/ # Static assets
7├── .eslintrc.json # ESLint configuration
8├── next.config.js # Next.js configuration
9├── package.json # Dependencies and scripts
10├── tailwind.config.ts # Tailwind CSS configuration
11├── tsconfig.json # TypeScript configuration
12└── README.md # Project documentation1cd my-production-app
2npm install1npm run devYour application will be available at http://localhost:3000.
Let's examine and optimize the tsconfig.json file for production use:
1{
2 "compilerOptions": {
3 "target": "es5",
4 "lib": ["dom", "dom.iterable", "es6"],
5 "allowJs": true,
6 "skipLibCheck": true,
7 "strict": true,
8 "noEmit": true,
9 "esModuleInterop": true,
10 "module": "esnext",
11 "moduleResolution": "bundler",
12 "resolveJsonModule": true,
13 "isolatedModules": true,
14 "jsx": "preserve",
15 "incremental": true,
16 "plugins": [
17 {
18 "name": "next"
19 }
20 ],
21 "baseUrl": ".",
22 "paths": {
23 "@/*": ["./src/*"],
24 "@/components/*": ["./src/components/*"],
25 "@/lib/*": ["./src/lib/*"],
26 "@/styles/*": ["./src/styles/*"]
27 }
28 },
29 "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
30 "exclude": ["node_modules"]
31}**strict: true** - Enables all strict type checking options
**noEmit: true** - Next.js handles compilation, so TypeScript only checks types
**baseUrl and paths** - Enable absolute imports and path mapping
**incremental: true** - Faster subsequent builds
Add these options for stricter type checking:
1{
2 "compilerOptions": {
3 // ... existing options
4 "noUnusedLocals": true,
5 "noUnusedParameters": true,
6 "noImplicitReturns": true,
7 "noFallthroughCasesInSwitch": true,
8 "exactOptionalPropertyTypes": true
9 }
10}src DirectoryCreate a src directory for better organization:
1mkdir src
2mv app src/Update tsconfig.json paths:
1{
2 "compilerOptions": {
3 "baseUrl": ".",
4 "paths": {
5 "@/*": ["./src/*"],
6 "@/app/*": ["./src/app/*"],
7 "@/components/*": ["./src/components/*"],
8 "@/lib/*": ["./src/lib/*"],
9 "@/hooks/*": ["./src/hooks/*"],
10 "@/types/*": ["./src/types/*"],
11 "@/utils/*": ["./src/utils/*"],
12 "@/styles/*": ["./src/styles/*"]
13 }
14 }
15}Enhance next.config.js for production readiness:
1/** @type {import('next').NextConfig} */
2const nextConfig = {
3 // Enable experimental features
4 experimental: {
5 typedRoutes: true,
6 },
7
8 // Compiler options
9 compiler: {
10 // Remove console.logs in production
11 removeConsole: process.env.NODE_ENV === 'production',
12 },
13
14 // Image optimization
15 images: {
16 domains: ['example.com'], // Add your image domains
17 formats: ['image/avif', 'image/webp'],
18 },
19
20 // Headers for security
21 async headers() {
22 return [
23 {
24 source: '/(.*)',
25 headers: [
26 {
27 key: 'X-Frame-Options',
28 value: 'DENY',
29 },
30 {
31 key: 'X-Content-Type-Options',
32 value: 'nosniff',
33 },
34 {
35 key: 'Referrer-Policy',
36 value: 'origin-when-cross-origin',
37 },
38 ],
39 },
40 ];
41 },
42
43 // Enable bundle analyzer in development
44 webpack: (config, { dev, isServer }) => {
45 if (dev && !isServer) {
46 const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
47 config.plugins.push(
48 new BundleAnalyzerPlugin({
49 analyzerMode: 'server',
50 openAnalyzer: false,
51 })
52 );
53 }
54 return config;
55 },
56};
57
58module.exports = nextConfig;1npx tsc --noEmit1npm run build1npm run devYour Next.js + TypeScript foundation is now ready for production development!
ESLint helps maintain code quality and consistency across your team. Let's configure it for production-ready React applications.
1npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-next eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-import eslint-plugin-jsx-a11yUpdate .eslintrc.json with comprehensive rules:
1{
2 "extends": [
3 "next/core-web-vitals",
4 "@typescript-eslint/recommended",
5 "@typescript-eslint/recommended-requiring-type-checking"
6 ],
7 "parser": "@typescript-eslint/parser",
8 "parserOptions": {
9 "project": "./tsconfig.json",
10 "tsconfigRootDir": "."
11 },
12 "plugins": [
13 "@typescript-eslint",
14 "react",
15 "react-hooks",
16 "import",
17 "jsx-a11y"
18 ],
19 "rules": {
20 // TypeScript specific rules
21 "@typescript-eslint/no-unused-vars": "error",
22 "@typescript-eslint/explicit-function-return-type": "warn",
23 "@typescript-eslint/no-explicit-any": "error",
24 "@typescript-eslint/prefer-const": "error",
25 "@typescript-eslint/no-inferrable-types": "error",
26
27 // React specific rules
28 "react/jsx-uses-react": "off",
29 "react/react-in-jsx-scope": "off",
30 "react/jsx-props-no-spreading": "warn",
31 "react/prop-types": "off",
32 "react/require-default-props": "off",
33 "react-hooks/rules-of-hooks": "error",
34 "react-hooks/exhaustive-deps": "warn",
35
36 // Import organization
37 "import/order": [
38 "error",
39 {
40 "groups": [
41 "builtin",
42 "external",
43 "internal",
44 "parent",
45 "sibling",
46 "index"
47 ],
48 "pathGroups": [
49 {
50 "pattern": "react",
51 "group": "external",
52 "position": "before"
53 },
54 {
55 "pattern": "@/**",
56 "group": "internal"
57 }
58 ],
59 "pathGroupsExcludedImportTypes": ["react"],
60 "newlines-between": "always",
61 "alphabetize": {
62 "order": "asc",
63 "caseInsensitive": true
64 }
65 }
66 ],
67
68 // General code quality
69 "no-console": ["warn", { "allow": ["warn", "error"] }],
70 "no-debugger": "error",
71 "prefer-const": "error",
72 "no-var": "error"
73 },
74 "env": {
75 "browser": true,
76 "es2021": true,
77 "node": true
78 },
79 "ignorePatterns": [
80 ".next",
81 "out",
82 "node_modules",
83 "*.config.js"
84 ]
85}Add scripts to package.json:
1{
2 "scripts": {
3 "lint": "eslint . --ext .ts,.tsx,.js,.jsx",
4 "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
5 "type-check": "tsc --noEmit"
6 }
7}Prettier ensures consistent code formatting across your entire codebase.
1npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettierCreate .prettierrc.json:
1{
2 "semi": true,
3 "trailingComma": "es5",
4 "singleQuote": true,
5 "printWidth": 80,
6 "tabWidth": 2,
7 "useTabs": false,
8 "bracketSpacing": true,
9 "bracketSameLine": false,
10 "arrowParens": "avoid",
11 "endOfLine": "lf",
12 "quoteProps": "as-needed",
13 "jsxSingleQuote": true,
14 "proseWrap": "preserve"
15}Create .prettierignore:
1# Dependencies
2node_modules/
3
4# Production builds
5.next/
6out/
7dist/
8build/
9
10# Environment files
11.env*
12
13# Logs
14*.log
15
16# Package manager files
17package-lock.json
18yarn.lock
19pnpm-lock.yaml
20
21# IDE files
22.vscode/
23.idea/
24
25# OS files
26.DS_Store
27Thumbs.dbAdd Prettier to ESLint configuration:
1{
2 "extends": [
3 "next/core-web-vitals",
4 "@typescript-eslint/recommended",
5 "@typescript-eslint/recommended-requiring-type-checking",
6 "prettier"
7 ],
8 "plugins": [
9 "@typescript-eslint",
10 "react",
11 "react-hooks",
12 "import",
13 "jsx-a11y",
14 "prettier"
15 ],
16 "rules": {
17 // ... existing rules
18 "prettier/prettier": "error"
19 }
20}Automate code quality checks before commits.
1npm install --save-dev husky lint-staged
2npx husky install1npx husky add .husky/pre-commit "npx lint-staged"Add to package.json:
1{
2 "lint-staged": {
3 "*.{ts,tsx,js,jsx}": [
4 "eslint --fix",
5 "prettier --write",
6 "git add"
7 ],
8 "*.{json,md,yaml,yml}": [
9 "prettier --write",
10 "git add"
11 ]
12 }
13}Create .vscode/settings.json for team consistency:
1{
2 "editor.defaultFormatter": "esbenp.prettier-vscode",
3 "editor.formatOnSave": true,
4 "editor.codeActionsOnSave": {
5 "source.fixAll.eslint": true,
6 "source.organizeImports": true
7 },
8 "typescript.preferences.importModuleSpecifier": "relative",
9 "emmet.includeLanguages": {
10 "typescript": "html",
11 "typescriptreact": "html"
12 }
13}Create .vscode/extensions.json for recommended extensions:
1{
2 "recommendations": [
3 "esbenp.prettier-vscode",
4 "dbaeumer.vscode-eslint",
5 "bradlc.vscode-tailwindcss",
6 "ms-vscode.vscode-typescript-next"
7 ]
8}Add comprehensive check scripts to package.json:
1{
2 "scripts": {
3 "dev": "next dev",
4 "build": "next build",
5 "start": "next start",
6 "lint": "eslint . --ext .ts,.tsx,.js,.jsx",
7 "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
8 "prettier": "prettier --check .",
9 "prettier:fix": "prettier --write .",
10 "type-check": "tsc --noEmit",
11 "quality": "npm run type-check && npm run lint && npm run prettier",
12 "quality:fix": "npm run type-check && npm run lint:fix && npm run prettier:fix"
13 }
14}Run these commands to verify everything works:
1# Check TypeScript types
2npm run type-check
3
4# Check ESLint rules
5npm run lint
6
7# Check Prettier formatting
8npm run prettier
9
10# Run all quality checks
11npm run quality
12
13# Fix all auto-fixable issues
14npm run quality:fixYour development environment is now configured for production-level code quality!
A well-organized project structure is crucial for maintainability and team collaboration. Let's create a production-ready folder architecture.
1src/
2├── app/ # Next.js App Router
3│ ├── (auth)/ # Route groups
4│ │ ├── login/
5│ │ └── register/
6│ ├── (dashboard)/
7│ │ ├── analytics/
8│ │ └── settings/
9│ ├── api/ # API routes
10│ │ ├── auth/
11│ │ └── users/
12│ ├── globals.css
13│ ├── layout.tsx
14│ └── page.tsx
15├── components/ # Reusable components
16│ ├── ui/ # Basic UI components
17│ │ ├── button.tsx
18│ │ ├── input.tsx
19│ │ └── modal.tsx
20│ ├── forms/ # Form components
21│ │ ├── auth-form.tsx
22│ │ └── contact-form.tsx
23│ ├── layout/ # Layout components
24│ │ ├── header.tsx
25│ │ ├── footer.tsx
26│ │ └── sidebar.tsx
27│ └── features/ # Feature-specific components
28│ ├── auth/
29│ ├── dashboard/
30│ └── profile/
31├── hooks/ # Custom React hooks
32│ ├── use-auth.ts
33│ ├── use-local-storage.ts
34│ └── use-api.ts
35├── lib/ # Utility libraries
36│ ├── api.ts
37│ ├── auth.ts
38│ ├── constants.ts
39│ ├── utils.ts
40│ └── validations.ts
41├── stores/ # State management
42│ ├── auth-store.ts
43│ ├── user-store.ts
44│ └── index.ts
45├── types/ # TypeScript type definitions
46│ ├── api.ts
47│ ├── auth.ts
48│ └── global.ts
49├── styles/ # Stylesheets
50│ ├── globals.css
51│ └── components.css
52└── tests/ # Test files
53 ├── __mocks__/
54 ├── components/
55 └── utils/1# Create main directories
2mkdir -p src/{components/{ui,forms,layout,features},hooks,lib,stores,types,styles,tests}
3
4# Create feature subdirectories
5mkdir -p src/components/features/{auth,dashboard,profile}
6
7# Create test subdirectories
8mkdir -p src/tests/{__mocks__,components,utils}src/components/ui/)Create reusable, generic UI components:
**Button Component (src/components/ui/button.tsx):**
1import { ButtonHTMLAttributes, forwardRef } from 'react';
2import { cn } from '@/lib/utils';
3
4interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
5 variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
6 size?: 'sm' | 'md' | 'lg';
7 isLoading?: boolean;
8}
9
10const Button = forwardRef<HTMLButtonElement, ButtonProps>(
11 ({ className, variant = 'primary', size = 'md', isLoading, children, ...props }, ref) => {
12 return (
13 <button
14 className={cn(
15 'inline-flex items-center justify-center rounded-md font-medium transition-colors',
16 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
17 'disabled:pointer-events-none disabled:opacity-50',
18 {
19 // Variants
20 'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
21 'bg-gray-100 text-gray-900 hover:bg-gray-200': variant === 'secondary',
22 'border border-gray-300 bg-transparent hover:bg-gray-50': variant === 'outline',
23 'hover:bg-gray-100': variant === 'ghost',
24
25 // Sizes
26 'h-8 px-3 text-sm': size === 'sm',
27 'h-10 px-4': size === 'md',
28 'h-12 px-6 text-lg': size === 'lg',
29 },
30 className
31 )}
32 ref={ref}
33 disabled={isLoading}
34 {...props}
35 >
36 {isLoading && (
37 <svg className="mr-2 h-4 w-4 animate-spin" viewBox="0 0 24 24">
38 <circle
39 className="opacity-25"
40 cx="12"
41 cy="12"
42 r="10"
43 stroke="currentColor"
44 strokeWidth="4"
45 />
46 <path
47 className="opacity-75"
48 fill="currentColor"
49 d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
50 />
51 </svg>
52 )}
53 {children}
54 </button>
55 );
56 }
57);
58
59Button.displayName = 'Button';
60
61export { Button, type ButtonProps };src/components/features/)Organize components by feature/domain:
**Auth Feature (src/components/features/auth/login-form.tsx):**
1'use client';
2
3import { useState } from 'react';
4import { useRouter } from 'next/navigation';
5import { Button } from '@/components/ui/button';
6import { Input } from '@/components/ui/input';
7import { useAuth } from '@/hooks/use-auth';
8import { loginSchema, type LoginData } from '@/lib/validations';
9
10export function LoginForm() {
11 const [formData, setFormData] = useState<LoginData>({
12 email: '',
13 password: '',
14 });
15 const [errors, setErrors] = useState<Partial<LoginData>>({});
16 const [isLoading, setIsLoading] = useState(false);
17
18 const { login } = useAuth();
19 const router = useRouter();
20
21 const handleSubmit = async (e: React.FormEvent) => {
22 e.preventDefault();
23 setIsLoading(true);
24 setErrors({});
25
26 try {
27 const validatedData = loginSchema.parse(formData);
28 await login(validatedData);
29 router.push('/dashboard');
30 } catch (error) {
31 if (error instanceof ZodError) {
32 setErrors(error.formErrors.fieldErrors);
33 }
34 } finally {
35 setIsLoading(false);
36 }
37 };
38
39 return (
40 <form onSubmit={handleSubmit} className="space-y-4">
41 <Input
42 type="email"
43 placeholder="Email"
44 value={formData.email}
45 onChange={(e) => setFormData({ ...formData, email: e.target.value })}
46 error={errors.email?.[0]}
47 />
48 <Input
49 type="password"
50 placeholder="Password"
51 value={formData.password}
52 onChange={(e) => setFormData({ ...formData, password: e.target.value })}
53 error={errors.password?.[0]}
54 />
55 <Button type="submit" isLoading={isLoading} className="w-full">
56 Sign In
57 </Button>
58 </form>
59 );
60}src/lib/)src/lib/utils.ts)1import { type ClassValue, clsx } from 'clsx';
2import { twMerge } from 'tailwind-merge';
3
4export function cn(...inputs: ClassValue[]) {
5 return twMerge(clsx(inputs));
6}
7
8export function formatDate(date: Date): string {
9 return new Intl.DateTimeFormat('en-US', {
10 year: 'numeric',
11 month: 'long',
12 day: 'numeric',
13 }).format(date);
14}
15
16export function capitalizeFirst(str: string): string {
17 return str.charAt(0).toUpperCase() + str.slice(1);
18}
19
20export function debounce<T extends (...args: any[]) => any>(
21 func: T,
22 wait: number
23): (...args: Parameters<T>) => void {
24 let timeout: NodeJS.Timeout;
25 return (...args: Parameters<T>) => {
26 clearTimeout(timeout);
27 timeout = setTimeout(() => func(...args), wait);
28 };
29}src/lib/constants.ts)1export const APP_CONFIG = {
2 name: 'My Production App',
3 description: 'A production-ready Next.js application',
4 url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
5 version: '1.0.0',
6} as const;
7
8export const API_ENDPOINTS = {
9 auth: {
10 login: '/api/auth/login',
11 logout: '/api/auth/logout',
12 register: '/api/auth/register',
13 },
14 users: {
15 profile: '/api/users/profile',
16 list: '/api/users',
17 },
18} as const;
19
20export const ROUTES = {
21 home: '/',
22 login: '/login',
23 register: '/register',
24 dashboard: '/dashboard',
25 profile: '/profile',
26} as const;src/types/)src/types/global.ts)1export interface User {
2 id: string;
3 email: string;
4 name: string;
5 role: 'admin' | 'user';
6 createdAt: Date;
7 updatedAt: Date;
8}
9
10export interface ApiResponse<T = any> {
11 data: T;
12 message: string;
13 success: boolean;
14}
15
16export interface PaginationMeta {
17 page: number;
18 limit: number;
19 total: number;
20 totalPages: number;
21}
22
23export interface PaginatedResponse<T> extends ApiResponse<T[]> {
24 meta: PaginationMeta;
25}src/types/api.ts)1export interface LoginRequest {
2 email: string;
3 password: string;
4}
5
6export interface LoginResponse {
7 user: User;
8 accessToken: string;
9 refreshToken: string;
10}
11
12export interface RegisterRequest {
13 email: string;
14 password: string;
15 name: string;
16}Create index.ts files for cleaner imports:
**src/components/ui/index.ts:**
1export { Button } from './button';
2export { Input } from './input';
3export { Modal } from './modal';**src/lib/index.ts:**
1export * from './utils';
2export * from './constants';
3export * from './validations';Your tsconfig.json should include these path mappings:
1{
2 "compilerOptions": {
3 "baseUrl": ".",
4 "paths": {
5 "@/*": ["./src/*"],
6 "@/components/*": ["./src/components/*"],
7 "@/components/ui": ["./src/components/ui"],
8 "@/hooks/*": ["./src/hooks/*"],
9 "@/lib/*": ["./src/lib/*"],
10 "@/types/*": ["./src/types/*"],
11 "@/styles/*": ["./src/styles/*"]
12 }
13 }
14}Your project structure is now optimized for scalability and maintainability!
Proper environment configuration is essential for production applications. Let's set up a robust environment management system.
1# Development environment
2.env.local # Local development (git-ignored)
3.env.development # Development defaults (committed)
4
5# Production environment
6.env.production # Production settings (committed, no secrets)
7
8# Example/template
9.env.example # Template for required variables (committed)**.env.example** (Template for team members):
1# App Configuration
2NEXT_PUBLIC_APP_URL=http://localhost:3000
3NEXT_PUBLIC_APP_NAME="My Production App"
4
5# Database
6DATABASE_URL=postgresql://username:password@localhost:5432/myapp
7DATABASE_POOL_SIZE=10
8
9# Authentication
10NEXTAUTH_SECRET=your-secret-key-here
11NEXTAUTH_URL=http://localhost:3000
12
13# External APIs
14STRIPE_SECRET_KEY=sk_test_...
15STRIPE_PUBLISHABLE_KEY=pk_test_...
16
17# Email Service
18SMTP_HOST=smtp.gmail.com
19SMTP_PORT=587
20SMTP_USER=[email protected]
21SMTP_PASS=your-app-password
22
23# Redis (for caching)
24REDIS_URL=redis://localhost:6379
25
26# Analytics
27NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
28
29# File Storage
30AWS_ACCESS_KEY_ID=your-access-key
31AWS_SECRET_ACCESS_KEY=your-secret-key
32AWS_BUCKET_NAME=my-app-bucket
33AWS_REGION=us-east-1**.env.development** (Development defaults):
1# App Configuration
2NEXT_PUBLIC_APP_URL=http://localhost:3000
3NEXT_PUBLIC_APP_NAME="My Production App (Dev)"
4
5# Database
6DATABASE_POOL_SIZE=5
7
8# Features flags
9NEXT_PUBLIC_FEATURE_ANALYTICS=false
10NEXT_PUBLIC_FEATURE_NOTIFICATIONS=true
11
12# Debug settings
13DEBUG=true
14LOG_LEVEL=debug**.env.production** (Production settings, no secrets):
1# App Configuration
2NEXT_PUBLIC_APP_NAME="My Production App"
3
4# Database
5DATABASE_POOL_SIZE=20
6
7# Feature flags
8NEXT_PUBLIC_FEATURE_ANALYTICS=true
9NEXT_PUBLIC_FEATURE_NOTIFICATIONS=true
10
11# Debug settings
12DEBUG=false
13LOG_LEVEL=error
14
15# Performance
16ENABLE_COMPRESSION=true
17ENABLE_CACHE=trueCreate src/lib/env.ts for type-safe environment variables:
1import { z } from 'zod';
2
3const envSchema = z.object({
4 // App configuration
5 NEXT_PUBLIC_APP_URL: z.string().url(),
6 NEXT_PUBLIC_APP_NAME: z.string().min(1),
7
8 // Database
9 DATABASE_URL: z.string().min(1),
10 DATABASE_POOL_SIZE: z.coerce.number().default(10),
11
12 // Authentication
13 NEXTAUTH_SECRET: z.string().min(32),
14 NEXTAUTH_URL: z.string().url(),
15
16 // External APIs
17 STRIPE_SECRET_KEY: z.string().optional(),
18 STRIPE_PUBLISHABLE_KEY: z.string().optional(),
19
20 // Email
21 SMTP_HOST: z.string().optional(),
22 SMTP_PORT: z.coerce.number().optional(),
23 SMTP_USER: z.string().email().optional(),
24 SMTP_PASS: z.string().optional(),
25
26 // Redis
27 REDIS_URL: z.string().optional(),
28
29 // AWS
30 AWS_ACCESS_KEY_ID: z.string().optional(),
31 AWS_SECRET_ACCESS_KEY: z.string().optional(),
32 AWS_BUCKET_NAME: z.string().optional(),
33 AWS_REGION: z.string().optional(),
34
35 // Feature flags
36 NEXT_PUBLIC_FEATURE_ANALYTICS: z.coerce.boolean().default(false),
37 NEXT_PUBLIC_FEATURE_NOTIFICATIONS: z.coerce.boolean().default(true),
38
39 // Debug
40 DEBUG: z.coerce.boolean().default(false),
41 LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
42});
43
44type Env = z.infer<typeof envSchema>;
45
46let env: Env;
47
48try {
49 env = envSchema.parse(process.env);
50} catch (error) {
51 console.error('❌ Invalid environment variables:', error);
52 process.exit(1);
53}
54
55export { env };
56
57// Type-safe access to environment variables
58export const config = {
59 app: {
60 url: env.NEXT_PUBLIC_APP_URL,
61 name: env.NEXT_PUBLIC_APP_NAME,
62 },
63 database: {
64 url: env.DATABASE_URL,
65 poolSize: env.DATABASE_POOL_SIZE,
66 },
67 auth: {
68 secret: env.NEXTAUTH_SECRET,
69 url: env.NEXTAUTH_URL,
70 },
71 features: {
72 analytics: env.NEXT_PUBLIC_FEATURE_ANALYTICS,
73 notifications: env.NEXT_PUBLIC_FEATURE_NOTIFICATIONS,
74 },
75 debug: {
76 enabled: env.DEBUG,
77 logLevel: env.LOG_LEVEL,
78 },
79} as const;Update next.config.js with production optimizations:
1const { env } = require('./src/lib/env');
2
3/** @type {import('next').NextConfig} */
4const nextConfig = {
5 // Output configuration
6 output: 'standalone', // For Docker deployments
7
8 // Experimental features
9 experimental: {
10 typedRoutes: true,
11 serverComponentsExternalPackages: ['@prisma/client'],
12 },
13
14 // Compiler optimizations
15 compiler: {
16 // Remove console.logs in production
17 removeConsole: process.env.NODE_ENV === 'production' ? {
18 exclude: ['error', 'warn'],
19 } : false,
20
21 // React strict mode
22 reactStrictMode: true,
23 },
24
25 // Performance optimizations
26 swcMinify: true,
27
28 // Image optimization
29 images: {
30 domains: ['images.unsplash.com', 'via.placeholder.com'],
31 formats: ['image/avif', 'image/webp'],
32 deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
33 imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
34 },
35
36 // Environment variables
37 env: {
38 BUILD_TIME: new Date().toISOString(),
39 BUILD_ID: process.env.VERCEL_GIT_COMMIT_SHA || 'local',
40 },
41
42 // Headers for security and performance
43 async headers() {
44 return [
45 {
46 source: '/(.*)',
47 headers: [
48 // Security headers
49 {
50 key: 'X-Frame-Options',
51 value: 'DENY',
52 },
53 {
54 key: 'X-Content-Type-Options',
55 value: 'nosniff',
56 },
57 {
58 key: 'Referrer-Policy',
59 value: 'origin-when-cross-origin',
60 },
61 {
62 key: 'X-XSS-Protection',
63 value: '1; mode=block',
64 },
65 // Performance headers
66 {
67 key: 'X-DNS-Prefetch-Control',
68 value: 'on',
69 },
70 ],
71 },
72 {
73 source: '/api/(.*)',
74 headers: [
75 {
76 key: 'Cache-Control',
77 value: 'no-store, max-age=0',
78 },
79 ],
80 },
81 ];
82 },
83
84 // Redirects
85 async redirects() {
86 return [
87 {
88 source: '/home',
89 destination: '/',
90 permanent: true,
91 },
92 ];
93 },
94
95 // Rewrites for API proxy
96 async rewrites() {
97 return [
98 {
99 source: '/api/proxy/:path*',
100 destination: 'https://external-api.com/:path*',
101 },
102 ];
103 },
104
105 // Webpack customization
106 webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
107 // Bundle analyzer in development
108 if (dev && !isServer) {
109 const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
110 config.plugins.push(
111 new BundleAnalyzerPlugin({
112 analyzerMode: 'server',
113 analyzerPort: isServer ? 8888 : 8889,
114 openAnalyzer: false,
115 })
116 );
117 }
118
119 // Production optimizations
120 if (!dev) {
121 config.optimization.splitChunks = {
122 chunks: 'all',
123 cacheGroups: {
124 default: false,
125 vendors: false,
126 vendor: {
127 name: 'vendor',
128 chunks: 'all',
129 test: /node_modules/,
130 },
131 },
132 };
133 }
134
135 return config;
136 },
137};
138
139module.exports = nextConfig;Create tsconfig.build.json for production builds:
1{
2 "extends": "./tsconfig.json",
3 "compilerOptions": {
4 "noUnusedLocals": true,
5 "noUnusedParameters": true,
6 "exactOptionalPropertyTypes": true
7 },
8 "include": [
9 "next-env.d.ts",
10 "**/*.ts",
11 "**/*.tsx",
12 ".next/types/**/*.ts"
13 ],
14 "exclude": [
15 "node_modules",
16 "**/*.test.ts",
17 "**/*.test.tsx",
18 "**/*.spec.ts",
19 "**/*.spec.tsx"
20 ]
21}Update your scripts for comprehensive build management:
1{
2 "scripts": {
3 // Development
4 "dev": "next dev",
5 "dev:turbo": "next dev --turbo",
6
7 // Building
8 "build": "next build",
9 "build:analyze": "ANALYZE=true next build",
10 "build:prod": "NODE_ENV=production next build",
11
12 // Type checking
13 "type-check": "tsc --noEmit",
14 "type-check:watch": "tsc --noEmit --watch",
15
16 // Linting
17 "lint": "next lint",
18 "lint:fix": "next lint --fix",
19
20 // Formatting
21 "prettier": "prettier --check .",
22 "prettier:fix": "prettier --write .",
23
24 // Testing
25 "test": "jest",
26 "test:watch": "jest --watch",
27 "test:coverage": "jest --coverage",
28
29 // Quality checks
30 "quality": "npm run type-check && npm run lint && npm run test",
31 "quality:fix": "npm run prettier:fix && npm run lint:fix && npm run type-check",
32
33 // Production
34 "start": "next start",
35 "preview": "npm run build && npm run start",
36
37 // Database
38 "db:generate": "prisma generate",
39 "db:push": "prisma db push",
40 "db:migrate": "prisma migrate dev",
41
42 // Utilities
43 "clean": "rm -rf .next out node_modules/.cache",
44 "reinstall": "rm -rf node_modules package-lock.json && npm install"
45 }
46}Create scripts/analyze-bundle.js:
1const { execSync } = require('child_process');
2const fs = require('fs');
3const path = require('path');
4
5console.log('🔍 Analyzing bundle size...
6');
7
8// Build the application
9execSync('npm run build', { stdio: 'inherit' });
10
11// Read build output
12const buildDir = path.join(process.cwd(), '.next');
13const statsFile = path.join(buildDir, 'analyze/client.json');
14
15if (fs.existsSync(statsFile)) {
16 const stats = JSON.parse(fs.readFileSync(statsFile, 'utf8'));
17
18 console.log('📊 Bundle Analysis Results:
19');
20 console.log(`Total Bundle Size: ${formatBytes(stats.assets.reduce((acc, asset) => acc + asset.size, 0))}`);
21 console.log(`Number of Chunks: ${stats.chunks.length}`);
22 console.log(`Number of Assets: ${stats.assets.length}
23`);
24
25 // Show largest assets
26 const largestAssets = stats.assets
27 .sort((a, b) => b.size - a.size)
28 .slice(0, 10);
29
30 console.log('🏆 Largest Assets:');
31 largestAssets.forEach((asset, index) => {
32 console.log(`${index + 1}. ${asset.name} - ${formatBytes(asset.size)}`);
33 });
34}
35
36function formatBytes(bytes) {
37 if (bytes === 0) return '0 Bytes';
38 const k = 1024;
39 const sizes = ['Bytes', 'KB', 'MB', 'GB'];
40 const i = Math.floor(Math.log(bytes) / Math.log(k));
41 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
42}Before deploying, verify:
Your application is now configured for production deployment!
Your progress is saved in this browser, on this device only.