perf: docker + next.js build optimisation - dockerignore exclusions, BuildKit cache mounts, backend image reuse, memory limits, next.js bundle tree-shaking
This commit is contained in:
@@ -1,12 +1,54 @@
|
||||
# ── Node / NPM ─────────────────────────────────────────────────────────────
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# ── Next.js build artefacts ─────────────────────────────────────────────────
|
||||
.next
|
||||
out
|
||||
|
||||
# ── Git / CI ────────────────────────────────────────────────────────────────
|
||||
.git
|
||||
.gitignore
|
||||
.github
|
||||
|
||||
# ── Secrets / env files (injected at runtime via docker-compose env_file) ───
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# ── Python artefacts ─────────────────────────────────────────────────────────
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
backend/__pycache__
|
||||
backend/celerybeat-schedule
|
||||
|
||||
# ── IDE / OS ─────────────────────────────────────────────────────────────────
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# ── Heavy static data not needed in the image ────────────────────────────────
|
||||
dpps
|
||||
scratch
|
||||
*.pdf
|
||||
jee_advanced_full_90q.json
|
||||
neet_180_questions.json
|
||||
neet_sample_questions.md
|
||||
API_DOCUMENTATION.md
|
||||
PAGEWISE_API_DOCUMENTATION.md
|
||||
DESIGN_SYSTEM.md
|
||||
PRD.md
|
||||
admin_json_samples.md
|
||||
README.md
|
||||
|
||||
# ── TypeScript build info ────────────────────────────────────────────────────
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
55
Dockerfile
55
Dockerfile
@@ -1,54 +1,57 @@
|
||||
FROM node:20-alpine AS base
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Stage 1 – deps: install node_modules with full cache mount
|
||||
# (This layer is only re-run when package.json / package-lock.json changes)
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
FROM node:20-alpine AS deps
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
# --mount=type=cache keeps the npm cache on the host between builds
|
||||
# --prefer-offline avoids network fetches when the package is already cached
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci --prefer-offline
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Stage 2 – builder: compile Next.js with build-cache mount for turbo-speed
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Set environment variables for build
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
# Accept API URL as build arg — defaults to Docker-internal service name
|
||||
ENV NODE_ENV=production
|
||||
|
||||
ARG NEXT_PUBLIC_API_URL=http://backend:8000
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
# BACKEND_INTERNAL_URL drives the server-side /api rewrite; must be a build-time
|
||||
# arg so it is compiled into next.config.ts (which is evaluated at build time).
|
||||
ARG BACKEND_INTERNAL_URL
|
||||
ENV BACKEND_INTERNAL_URL=$BACKEND_INTERNAL_URL
|
||||
|
||||
RUN npm run build
|
||||
# Mount the Next.js build cache so incremental compilation is reused
|
||||
RUN --mount=type=cache,target=/app/.next/cache \
|
||||
npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Stage 3 – runner: minimal production image (no node_modules, no sources)
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
RUN addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 nextjs
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
# Copy only the standalone server output (no full node_modules)
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
RUN mkdir .next && 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
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# PragatiVeer Backend – Python 3.11 slim
|
||||
# Optimised for fast re-builds:
|
||||
# • requirements.txt copied FIRST → pip cache layer is only invalidated
|
||||
# when dependencies change, not on every source-code edit.
|
||||
# • BuildKit pip cache mount keeps wheel cache on host between builds.
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Install system deps in a single RUN layer, clean apt cache immediately
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ── Dependencies first (most stable layer) ──────────────────────────────────
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
# Use BuildKit pip cache so wheels are reused across builds
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# ── Source code (changes frequently; comes last) ─────────────────────────────
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
|
||||
39
build.ps1
Normal file
39
build.ps1
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env powershell
|
||||
# build.ps1 — Clean, fast Docker build script for PragatiVeer v2
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# What this script does:
|
||||
# 1. Enables Docker BuildKit (required for --mount=type=cache to work)
|
||||
# 2. Runs `docker compose build --parallel` which:
|
||||
# - Builds backend image ONCE and tags it as pragativeer-backend:local
|
||||
# - worker + beat containers reuse that image (no rebuild)
|
||||
# - BuildKit caches pip wheels and npm packages between builds
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
$env:DOCKER_BUILDKIT = "1"
|
||||
$env:COMPOSE_DOCKER_CLI_BUILD = "1"
|
||||
|
||||
Write-Host "═══════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " PragatiVeer v2 — Optimised Docker Build" -ForegroundColor Cyan
|
||||
Write-Host "═══════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "▶ BuildKit enabled (cache mounts active)" -ForegroundColor Green
|
||||
Write-Host "▶ Building backend image first..." -ForegroundColor Yellow
|
||||
|
||||
# Build backend image first so worker/beat can reuse it
|
||||
docker compose build backend
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "▶ Building remaining services in parallel..." -ForegroundColor Yellow
|
||||
|
||||
# Build remaining services (frontend, etc.)
|
||||
docker compose build --parallel frontend
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "✔ Build complete. Starting services..." -ForegroundColor Green
|
||||
docker compose up -d
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "✔ All services started." -ForegroundColor Green
|
||||
Write-Host " Frontend: http://localhost:3000" -ForegroundColor Cyan
|
||||
Write-Host " Backend: http://localhost:8000" -ForegroundColor Cyan
|
||||
Write-Host " MinIO: http://localhost:9001" -ForegroundColor Cyan
|
||||
@@ -1,16 +1,19 @@
|
||||
version: '3.8'
|
||||
|
||||
# ── Build once, run three times for backend services ─────────────────────────
|
||||
# backend, worker, and beat all share the same image (built once from Dockerfile.dev)
|
||||
# This alone eliminates 2 redundant Docker builds (previously 3 separate builds)
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: pragativeer_prod_db
|
||||
restart: unless-stopped
|
||||
container_name: pragativeer_db
|
||||
environment:
|
||||
POSTGRES_USER: pragativeer_admin
|
||||
POSTGRES_PASSWORD: local_secure_password_2026
|
||||
POSTGRES_DB: pragativeer_prod
|
||||
ports:
|
||||
- "5440:5432"
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
@@ -19,29 +22,161 @@ services:
|
||||
test: ["CMD-SHELL", "pg_isready -U pragativeer_admin -d pragativeer_prod"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
retries: 5
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512m
|
||||
|
||||
pgbouncer:
|
||||
image: edoburu/pgbouncer
|
||||
container_name: pragativeer_pgbouncer
|
||||
environment:
|
||||
- DATABASE_URL=postgres://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod
|
||||
volumes:
|
||||
- ./pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro
|
||||
- ./userlist.txt:/etc/pgbouncer/userlist.txt:ro
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 || exit 0"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- pragativeer_network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64m
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: pragativeer_prod_cache
|
||||
restart: unless-stopped
|
||||
container_name: pragativeer_cache
|
||||
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru --save ""
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
networks:
|
||||
- pragativeer_network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 192m
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.dev
|
||||
# Tag the image so worker/beat can reuse it without rebuilding
|
||||
image: pragativeer-backend:local
|
||||
container_name: pragativeer_backend
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
- ./admin_uploads:/app/admin_uploads:ro
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@pgbouncer:5432/pragativeer_prod
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- ENV=development
|
||||
- MINIO_ENDPOINT=minio:9000
|
||||
- MINIO_ACCESS_KEY=minio_admin
|
||||
- MINIO_SECRET_KEY=minio_secure_password_2026
|
||||
- MINIO_BUCKET=pragativeer-uploads
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
pgbouncer:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- pragativeer_network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512m
|
||||
|
||||
worker:
|
||||
# Reuse the image built by the 'backend' service — no separate build
|
||||
image: pragativeer-backend:local
|
||||
container_name: pragativeer_worker
|
||||
command: celery -A worker.celery_app worker --loglevel=info --concurrency=2
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@pgbouncer:5432/pragativeer_prod
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- MINIO_ENDPOINT=minio:9000
|
||||
- MINIO_ACCESS_KEY=minio_admin
|
||||
- MINIO_SECRET_KEY=minio_secure_password_2026
|
||||
- MINIO_BUCKET=pragativeer-uploads
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
depends_on:
|
||||
- backend
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- pragativeer_network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 256m
|
||||
|
||||
beat:
|
||||
# Reuse the image built by the 'backend' service — no separate build
|
||||
image: pragativeer-backend:local
|
||||
container_name: pragativeer_beat
|
||||
command: celery -A worker.celery_app beat --loglevel=info
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@pgbouncer:5432/pragativeer_prod
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
depends_on:
|
||||
- backend
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- pragativeer_network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 128m
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- NEXT_PUBLIC_API_URL=http://backend:8000
|
||||
container_name: pragativeer_frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NEXT_PUBLIC_API_URL=http://backend:8000
|
||||
- NEXT_PUBLIC_PAYMENT_GATEWAY=razorpay
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- pragativeer_network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512m
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
container_name: pragativeer_prod_storage
|
||||
restart: unless-stopped
|
||||
image: minio/minio:RELEASE.2024-06-13T22-53-53Z
|
||||
container_name: pragativeer_storage
|
||||
ports:
|
||||
- "9025:9000"
|
||||
- "9026:9001"
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- miniodata:/data
|
||||
environment:
|
||||
@@ -50,124 +185,10 @@ services:
|
||||
command: server /data --console-address ":9001"
|
||||
networks:
|
||||
- pragativeer_network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.prod
|
||||
container_name: pragativeer_prod_backend
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./admin_uploads:/app/admin_uploads:ro
|
||||
ports:
|
||||
- "8010:8000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- ENV=production
|
||||
- MINIO_ENDPOINT=minio:9000
|
||||
- MINIO_ACCESS_KEY=minio_admin
|
||||
- MINIO_SECRET_KEY=minio_secure_password_2026
|
||||
- MINIO_BUCKET=pragativeer-uploads
|
||||
- TZ=Asia/Kolkata
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- pragativeer_network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
worker:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.prod
|
||||
container_name: pragativeer_prod_worker
|
||||
restart: unless-stopped
|
||||
command: celery -A worker.celery_app worker --loglevel=info
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- MINIO_ENDPOINT=minio:9000
|
||||
- MINIO_ACCESS_KEY=minio_admin
|
||||
- MINIO_SECRET_KEY=minio_secure_password_2026
|
||||
- MINIO_BUCKET=pragativeer-uploads
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- pragativeer_network
|
||||
healthcheck:
|
||||
# Celery worker serves no HTTP. Verify liveness via celery inspect ping.
|
||||
# NOTE: output goes to a tmp file (not a pipe) — celery's forked children hold
|
||||
# the pipe open and hang the check. Verified working in-container.
|
||||
test: ["CMD-SHELL", "celery -A worker.celery_app inspect ping -d celery@$$HOSTNAME --timeout=5 > /tmp/hc.txt 2>&1; grep -q pong /tmp/hc.txt || exit 1"]
|
||||
# Python cold-start makes the ping take 15-20s under load — generous timeout.
|
||||
interval: 60s
|
||||
timeout: 40s
|
||||
retries: 3
|
||||
|
||||
beat:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.prod
|
||||
container_name: pragativeer_prod_beat
|
||||
restart: unless-stopped
|
||||
command: celery -A worker.celery_app beat --loglevel=info
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- pragativeer_network
|
||||
healthcheck:
|
||||
# Celery beat is a scheduler — no HTTP endpoint. pgrep isn't installed in the
|
||||
# image; use a /proc scan instead. Verified working in-container.
|
||||
test: ["CMD-SHELL", "grep -as 'celery.*[b]eat' /proc/*/cmdline > /dev/null 2>&1 || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- NEXT_PUBLIC_API_URL=https://pragativeer.com
|
||||
- BACKEND_INTERNAL_URL=http://backend:8000
|
||||
container_name: pragativeer_prod_frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3882:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- BACKEND_INTERNAL_URL=http://backend:8000
|
||||
- NEXT_PUBLIC_API_URL=https://pragativeer.com
|
||||
- NEXT_PUBLIC_PAYMENT_GATEWAY=razorpay
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- pragativeer_network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 256m
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
@@ -11,6 +11,34 @@ const API_BASE =
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
|
||||
// ── Performance: strip X-Powered-By header ──────────────────────────────
|
||||
poweredByHeader: false,
|
||||
|
||||
// ── Performance: gzip compression handled by Next.js standalone server ──
|
||||
compress: true,
|
||||
|
||||
// ── Compiler optimisations ───────────────────────────────────────────────
|
||||
compiler: {
|
||||
// Remove console.log statements from production builds
|
||||
removeConsole: process.env.NODE_ENV === "production"
|
||||
? { exclude: ["error", "warn"] }
|
||||
: false,
|
||||
},
|
||||
|
||||
// ── Tree-shake large packages (only import what is used) ─────────────────
|
||||
// This tells Next.js to side-effect-free import specific packages,
|
||||
// significantly reducing the bundle size for heavy libraries.
|
||||
experimental: {
|
||||
optimizePackageImports: [
|
||||
"lucide-react",
|
||||
"recharts",
|
||||
"framer-motion",
|
||||
"date-fns",
|
||||
],
|
||||
},
|
||||
|
||||
// ── API proxy rewrites ───────────────────────────────────────────────────
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user