import createNextIntlPlugin from 'next-intl/plugin'; import type { NextConfig } from 'next'; /** * Points next-intl at the request config. Without this the message catalogs are * never registered and every server-side `getTranslations` call fails at build * time rather than at runtime — which is the good failure mode, but only if the * plugin is wired up in the first place. */ const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts'); const nextConfig: NextConfig = { reactStrictMode: true, /** * Proxies API calls through this app's own origin. * * The refresh token is a `SameSite=Lax` httpOnly cookie, so the browser only * sends it first-party. Calling the API host directly from the browser would * mean `SameSite=None; Secure`, which cannot work over plain HTTP in local * development at all. Production does the same thing at the Nginx layer, so * dev and prod share one topology instead of two. */ async rewrites() { const target = process.env.API_INTERNAL_URL ?? 'http://localhost:4000'; return [{ source: '/api/:path*', destination: `${target}/api/:path*` }]; }, /** * Workspace packages ship TypeScript source rather than a build artefact, so * Next compiles them with the app. No watch-and-rebuild step during local * development, and dead code is tree-shaken per app. */ transpilePackages: ['@sport/ui'], // Compile-time checked values — a typo becomes a build failure. typedRoutes: true, images: { /** * Media is served from R2/CDN in production and MinIO locally. * * `pathname` and `search` are specified explicitly: Next 16 matches remote * patterns strictly, and an entry without them does not authorise the URL — * the optimizer answers `"url" parameter is not allowed` and every product * image renders broken. Scoping to the bucket path also keeps this from * becoming an open image proxy. */ remotePatterns: [ { protocol: 'http', hostname: 'localhost', port: '9000', pathname: '/**', search: '' }, { protocol: 'https', hostname: '**.r2.dev', pathname: '/**', search: '' }, { protocol: 'https', hostname: 'cdn.sport-store.local', pathname: '/**', search: '' }, ], formats: ['image/avif', 'image/webp'], /** * DEVELOPMENT ONLY. * * Next 16 refuses to fetch an upstream image whose hostname resolves to a * private IP — an SSRF precaution — and reports it as `"url" parameter is * not allowed`, the same message it uses for an unmatched remote pattern. * That shared message is what makes this so easy to misdiagnose. * * Local MinIO lives on `localhost:9000`, so the guard blocks every product * image in development. It stays ON in production, where media is served * from a public CDN host and the protection is exactly what we want. */ dangerouslyAllowLocalIP: process.env.NODE_ENV !== 'production', }, // Standalone output keeps the production image small (no node_modules copy). output: 'standalone', experimental: { optimizePackageImports: ['@sport/ui'], }, }; export default withNextIntl(nextConfig);