80 lines
3.1 KiB
TypeScript
80 lines
3.1 KiB
TypeScript
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 <Link href> 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);
|