Next.js + React + TypeScript Setup and Configuration
Overview
Next.js 14+ setup with App Router, TypeScript, and modern React patterns, following AzmX development standards for high-performance web applications.
Core Dependencies
From typical Next.js package.json:
{
"dependencies": {
"next": "14.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"typescript": "~5.8.3",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"next-auth": "^4.24.0",
"axios": "^1.6.0",
"swr": "^2.2.4",
"react-hook-form": "^7.48.0",
"@hookform/resolvers": "^3.3.0",
"zod": "^3.22.0",
"lucide-react": "^0.294.0"
},
"devDependencies": {
"eslint": "^8.56.0",
"eslint-config-next": "14.2.0",
"prettier": "^3.1.0",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"jest": "^29.7.0",
"@testing-library/react": "^14.1.0",
"@testing-library/jest-dom": "^6.1.0"
}
}
Project Structure
project-name/
├── package.json
├── next.config.js
├── tsconfig.json
├── tailwind.config.ts
├── postcss.config.js
├── .env.local
├── .env.example
├── public/
│ ├── favicon.ico
│ ├── icons/
│ └── images/
├── src/
│ ├── app/ # App Router directory
│ │ ├── globals.css # Global styles
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ ├── loading.tsx # Global loading UI
│ │ ├── error.tsx # Global error UI
│ │ ├── not-found.tsx # 404 page
│ │ ├── (dashboard)/ # Route groups
│ │ │ ├── dashboard/
│ │ │ │ ├── page.tsx
│ │ │ │ └── loading.tsx
│ │ │ └── layout.tsx
│ │ ├── api/ # API routes
│ │ │ ├── auth/
│ │ │ │ └── route.ts
│ │ │ └── users/
│ │ │ └── route.ts
│ │ └── (auth)/
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── components/ # React components
│ │ ├── ui/ # Base UI components
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ │ ├── Card.tsx
│ │ │ └── index.ts
│ │ ├── forms/ # Form components
│ │ │ ├── LoginForm.tsx
│ │ │ └── UserForm.tsx
│ │ ├── layouts/ # Layout components
│ │ │ ├── Header.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ └── Footer.tsx
│ │ └── features/ # Feature-specific components
│ │ ├── auth/
│ │ └── dashboard/
│ ├── lib/ # Utility functions
│ │ ├── auth.ts # Authentication utilities
│ │ ├── api.ts # API client
│ │ ├── utils.ts # Common utilities
│ │ └── validations.ts # Zod schemas
│ ├── hooks/ # Custom React hooks
│ │ ├── useAuth.ts
│ │ ├── useApi.ts
│ │ └── index.ts
│ ├── providers/ # Context providers
│ │ ├── AuthProvider.tsx
│ │ └── ThemeProvider.tsx
│ ├── types/ # TypeScript type definitions
│ │ ├── auth.ts
│ │ ├── api.ts
│ │ └── index.ts
│ └── styles/ # Additional styles
│ └── components.css
└── __tests__/ # Test files
├── components/
└── pages/
Configuration Files
Next.js Configuration (next.config.js)
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
serverComponentsExternalPackages: [],
},
images: {
domains: ['images.unsplash.com', 'via.placeholder.com'],
formats: ['image/webp', 'image/avif'],
},
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
// Production optimizations
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
// Bundle analyzer (optional)
...(process.env.ANALYZE === 'true' && {
webpack: (config) => {
config.plugins.push(
new (require('@next/bundle-analyzer')({
enabled: true,
}))()
);
return config;
},
}),
}
module.exports = nextConfig
TypeScript Configuration (tsconfig.json)
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"],
"@/hooks/*": ["./src/hooks/*"],
"@/types/*": ["./src/types/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Tailwind CSS Configuration (tailwind.config.ts)
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
},
secondary: {
50: '#f8fafc',
500: '#64748b',
600: '#475569',
700: '#334155',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
spacing: {
'18': '4.5rem',
'88': '22rem',
},
animation: {
'fade-in': 'fadeIn 0.5s ease-in-out',
'slide-up': 'slideUp 0.3s ease-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(10px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}
export default config
App Router Setup
Root Layout (src/app/layout.tsx)
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { AuthProvider } from '@/providers/AuthProvider'
import { ThemeProvider } from '@/providers/ThemeProvider'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: {
template: '%s | AzmX App',
default: 'AzmX Application',
},
description: 'Modern web application built with Next.js',
keywords: ['Next.js', 'React', 'TypeScript', 'Tailwind CSS'],
authors: [{ name: 'AzmX Team' }],
creator: 'AzmX',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<AuthProvider>
<div className="min-h-screen bg-background">
{children}
</div>
</AuthProvider>
</ThemeProvider>
</body>
</html>
)
}
Home Page (src/app/page.tsx)
import { Metadata } from 'next'
import Link from 'next/link'
import { Button } from '@/components/ui/Button'
import { Card } from '@/components/ui/Card'
export const metadata: Metadata = {
title: 'Home',
description: 'Welcome to AzmX Application',
}
export default function HomePage() {
return (
<main className="container mx-auto px-4 py-8">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
Welcome to AzmX
</h1>
<p className="text-xl text-gray-600 mb-6">
Modern web application built with Next.js 14
</p>
<div className="flex gap-4 justify-center">
<Button asChild>
<Link href="/dashboard">Get Started</Link>
</Button>
<Button variant="outline" asChild>
<Link href="/about">Learn More</Link>
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12">
<Card className="p-6">
<h3 className="text-lg font-semibold mb-2">Fast Development</h3>
<p className="text-gray-600">
Built with Next.js 14 App Router for optimal performance
</p>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold mb-2">Type Safety</h3>
<p className="text-gray-600">
Full TypeScript support with strict type checking
</p>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold mb-2">Modern UI</h3>
<p className="text-gray-600">
Beautiful components built with Tailwind CSS
</p>
</Card>
</div>
</main>
)
}
API Routes Setup
Authentication API Route (src/app/api/auth/route.ts)
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
})
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { email, password } = loginSchema.parse(body)
// TODO: Implement actual authentication logic
// This would typically involve:
// 1. Validating credentials against database
// 2. Creating JWT token
// 3. Setting secure cookies
if (email === '[email protected]' && password === 'password') {
return NextResponse.json({
success: true,
user: { id: 1, email, name: 'Admin User' },
token: 'mock-jwt-token'
})
}
return NextResponse.json(
{ error: 'Invalid credentials' },
{ status: 401 }
)
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
Users API Route (src/app/api/users/route.ts)
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
role: z.enum(['admin', 'user']).optional(),
})
export async function GET(request: NextRequest) {
// TODO: Implement user fetching logic
// This would typically involve:
// 1. Authentication check
// 2. Database query
// 3. Response formatting
const users = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'user' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'admin' },
]
return NextResponse.json({ users })
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const userData = createUserSchema.parse(body)
// TODO: Implement user creation logic
const newUser = {
id: Date.now(),
...userData,
role: userData.role || 'user',
createdAt: new Date().toISOString(),
}
return NextResponse.json(newUser, { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
Component Examples
UI Button Component (src/components/ui/Button.tsx)
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
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 ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
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 bg-background 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: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = 'Button'
export { Button, buttonVariants }
Environment Configuration
Development Environment (.env.local)
# App Configuration
NEXT_PUBLIC_APP_NAME=AzmX Development
NEXT_PUBLIC_APP_URL=http://localhost:3000
NODE_ENV=development
# API Configuration
NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1
API_SECRET_KEY=your-development-secret-key
# Database (if using direct DB connection)
DATABASE_URL=postgresql://postgres:password@localhost:5433/nextjs_dev
# Authentication
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-nextauth-secret-key
JWT_SECRET=your-jwt-secret-key
# External Services
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/project
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
# Feature Flags
NEXT_PUBLIC_ENABLE_ANALYTICS=false
NEXT_PUBLIC_ENABLE_MONITORING=true
Production Environment (.env.production)
# App Configuration
NEXT_PUBLIC_APP_NAME=AzmX Production
NEXT_PUBLIC_APP_URL=https://app.azmx.sa
NODE_ENV=production
# API Configuration
NEXT_PUBLIC_API_URL=https://api.azmx.sa/api/v1
API_SECRET_KEY=${PRODUCTION_SECRET_KEY}
# Database
DATABASE_URL=${PRODUCTION_DATABASE_URL}
# Authentication
NEXTAUTH_URL=https://app.azmx.sa
NEXTAUTH_SECRET=${PRODUCTION_NEXTAUTH_SECRET}
JWT_SECRET=${PRODUCTION_JWT_SECRET}
# External Services
NEXT_PUBLIC_SENTRY_DSN=${PRODUCTION_SENTRY_DSN}
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=${PRODUCTION_GA_ID}
# Feature Flags
NEXT_PUBLIC_ENABLE_ANALYTICS=true
NEXT_PUBLIC_ENABLE_MONITORING=true
Authentication Setup
Auth Provider (src/providers/AuthProvider.tsx)
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { User } from '@/types/auth'
interface AuthContextType {
user: User | null
login: (email: string, password: string) => Promise<void>
logout: () => void
loading: boolean
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// Check for existing session
const token = localStorage.getItem('token')
if (token) {
// TODO: Verify token with API
// For now, mock user data
setUser({ id: 1, email: '[email protected]', name: 'User' })
}
setLoading(false)
}, [])
const login = async (email: string, password: string) => {
const response = await fetch('/api/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (!response.ok) {
throw new Error('Login failed')
}
const data = await response.json()
localStorage.setItem('token', data.token)
setUser(data.user)
}
const logout = () => {
localStorage.removeItem('token')
setUser(null)
}
return (
<AuthContext.Provider value={{ user, login, logout, loading }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}
Testing Configuration
Jest Configuration (jest.config.js)
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files
dir: './',
})
// Add any custom config to be passed to Jest
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapping: {
'^@/components/(.*)$': '<rootDir>/src/components/$1',
'^@/pages/(.*)$': '<rootDir>/src/pages/$1',
'^@/lib/(.*)$': '<rootDir>/src/lib/$1',
'^@/hooks/(.*)$': '<rootDir>/src/hooks/$1',
'^@/types/(.*)$': '<rootDir>/src/types/$1',
},
testEnvironment: 'jest-environment-jsdom',
}
module.exports = createJestConfig(customJestConfig)
Test Example (tests/components/Button.test.tsx)
import { render, screen } from '@testing-library/react'
import { Button } from '@/components/ui/Button'
describe('Button', () => {
it('renders a button', () => {
render(<Button>Click me</Button>)
const button = screen.getByRole('button', { name: /click me/i })
expect(button).toBeInTheDocument()
})
it('applies the correct variant class', () => {
render(<Button variant="destructive">Delete</Button>)
const button = screen.getByRole('button', { name: /delete/i })
expect(button).toHaveClass('bg-destructive')
})
it('can be disabled', () => {
render(<Button disabled>Disabled</Button>)
const button = screen.getByRole('button', { name: /disabled/i })
expect(button).toBeDisabled()
expect(button).toHaveClass('disabled:pointer-events-none')
})
})
Performance Optimizations
Image Optimization
import Image from 'next/image'
export function OptimizedImage() {
return (
<Image
src="/images/hero.jpg"
alt="Hero image"
width={800}
height={600}
priority // Above the fold images
placeholder="blur" // Show blur while loading
blurDataURL="data:image/jpeg;base64,..." // Base64 blur image
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
)
}
Bundle Analysis
# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer
# Analyze bundle
ANALYZE=true npm run build
Development Commands
Useful Scripts (package.json)
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"type-check": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"analyze": "ANALYZE=true npm run build"
}
}
Deployment Configuration
Vercel Deployment (vercel.json)
{
"framework": "nextjs",
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"env": {
"NEXT_PUBLIC_API_URL": "@api-url-production",
"DATABASE_URL": "@database-url-production",
"NEXTAUTH_SECRET": "@nextauth-secret-production"
},
"functions": {
"src/app/api/**/*.ts": {
"maxDuration": 30
}
},
"rewrites": [
{
"source": "/api/:path*",
"destination": "/api/:path*"
}
]
}
Docker Configuration (Dockerfile)
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci --only=production
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build the application
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]