import type { NextConfig } from "next"; const nextConfig: NextConfig = { // Enable standalone output for Docker/PM2 deployment output: "standalone", // Lock the file-tracing root to the project directory. Without this, // Next.js 16 walks up from package.json looking for a lockfile, finds // the homelab runner's stale `act` cache at // /home/tyler/.cache/act/.../package-lock.json, and warns: // "We detected multiple lockfiles and selected the directory of // /home/tyler/package-lock.json as the root directory." // The deploy runner's APP_DIR is /home/tyler/route-commerce, so // resolving relative to the project root is correct both locally and // in CI. We resolve to an absolute path to avoid the warning in // Next.js 16 which prefers absolute paths here. outputFileTracingRoot: __dirname, // Enable strict mode reactStrictMode: true, // Optimize images images: { remotePatterns: [ { protocol: "https", hostname: "*.supabase.co", }, { protocol: "https", hostname: "images.unsplash.com", }, { protocol: "https", hostname: "picsum.photos", }, ], formats: ["image/avif", "image/webp"], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], }, // Security headers async headers() { return [ { source: "/:path*", headers: [ { key: "X-Content-Type-Options", value: "nosniff", }, { key: "X-Frame-Options", value: "DENY", }, { key: "X-XSS-Protection", value: "1; mode=block", }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin", }, { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()", }, ], }, { // API routes - more restrictive source: "/api/:path*", headers: [ { key: "Access-Control-Allow-Methods", value: "GET, POST, PUT, DELETE, OPTIONS", }, { key: "Access-Control-Allow-Headers", value: "Content-Type, Authorization, X-API-Key", }, { key: "Access-Control-Max-Age", value: "86400", }, ], }, { source: "/_next/static/:path*", headers: [ { key: "Cache-Control", value: "public, max-age=31536000, immutable" }, ], }, { source: "/icons/:path*", headers: [ { key: "Cache-Control", value: "public, max-age=31536000, immutable" }, ], }, { source: "/screenshots/:path*", headers: [ { key: "Cache-Control", value: "public, max-age=604800" }, ], }, ]; }, // Redirects async redirects() { return [ // Cutover (PR 6, Task 6.1): mobile-first admin lives at /admin/v2/*. // Redirect the v1 list/detail paths to their v2 equivalents. // Use `permanent: false` (307) so we can revert easily if a // regression is caught in monitoring before the v1 files are // removed in Task 6.2. // PR 8, Task 8.1: redirect the bare /admin path to the v2 dashboard. // The dashboard itself still lives at /admin (as the v1 page) // until Task 8.2 removes it after a 3-day monitoring window — // this redirect just points the v1 entry point at v2. { source: "/admin", destination: "/admin/v2", permanent: false }, { source: "/admin/orders", destination: "/admin/v2/orders", permanent: false }, { source: "/admin/orders/:id", destination: "/admin/v2/orders/:id", permanent: false }, { source: "/admin/stops", destination: "/admin/v2/stops", permanent: false }, { source: "/admin/products", destination: "/admin/v2/products", permanent: false }, // v1 admin pages that hit the dead supabase shim. There are no v2 // equivalents yet (reports / taxes / settings sub-pages are still // TBD on the v2 surface), so they redirect to the v2 dashboard // for now. Once those modules ship on v2, the redirects can be // tightened to point at the new pages. { source: "/admin/products/:id", destination: "/admin/v2/products", permanent: false }, { source: "/admin/reports", destination: "/admin/v2", permanent: false }, { source: "/admin/taxes", destination: "/admin/v2", permanent: false }, { source: "/admin/settings/shipping", destination: "/admin/settings", permanent: false }, { source: "/admin/settings/integrations", destination: "/admin/settings", permanent: false }, { source: "/admin/settings/billing", destination: "/admin/settings", permanent: false }, ]; }, // Rewrites for API proxy async rewrites() { return [ // Add any necessary rewrites here ]; }, // Experimental features experimental: { // Enable optimizePackageImports for better bundle size optimizePackageImports: ["lucide-react", "@radix-ui/react-icons", "framer-motion"], // Enable React's and Next.js' automatic route // transitions. Combined with the smooth-transition wrappers around // page content (see src/components/transitions), navigation feels // like a single continuous app rather than a sequence of page loads. viewTransition: true, }, // Compiler options compiler: { // Remove console.log in production removeConsole: process.env.NODE_ENV === "production" ? { exclude: ["error", "warn"] } : false, }, // Logging logging: { fetches: { fullUrl: process.env.NODE_ENV === "development", }, }, }; export default nextConfig;