next.config.ts
Rama main · réplica de lectura del repositorio original.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactStrictMode: true,
// The React Compiler memoises components for us, which is only sound if every
// component obeys the rules of React. That is why `eslint-plugin-react-hooks`
// is on at its full ruleset rather than just rules-of-hooks: a component the
// compiler cannot prove safe is silently skipped, so the lint errors are the
// only place that shows up. It adds a Babel pass, so builds are slower.
experimental: { reactCompiler: true },
// pg is only used by the local preview driver. Keep it external so webpack
// does not try to bundle its optional native bits into the production build.
//
// web-push for the same reason from the other direction: it is CommonJS and
// reaches for node:crypto and node:http directly to do the ECDH and the
// signing, none of which survives being bundled for an edge-ish target.
serverExternalPackages: ["pg", "web-push"],
// The OG card reads a TTF off disk at runtime. Nothing imports it, so the
// tracer cannot see it and the file would be absent from the deployment: the
// card would 500 in production and be fine everywhere it was tested.
outputFileTracingIncludes: {
"/opengraph-image": ["./assets/fonts/**"],
},
// Response headers, on every route.
//
// Deliberately the boring ones. There is no full Content-Security-Policy
// here: Next's App Router emits inline bootstrap scripts, so a real policy
// needs a per-request nonce through middleware, and a wrong one fails as a
// blank page rather than as an error. `frame-ancestors` is the one directive
// that costs nothing to get right, and it is the one that matters most,
// because the whole app is one-click actions behind a session cookie.
//
// Two that are absent on purpose. `Cross-Origin-Opener-Policy` can break an
// OAuth flow that uses a popup, and this one is a redirect today but need not
// stay one. `preload` on HSTS is a submission to a browser list that is
// difficult to leave, and it is not ours to make for a domain serving other
// things.
// Next types this as returning a promise, and there is nothing to await.
headers: () =>
Promise.resolve([
{
source: "/:path*",
headers: [
// Nothing may frame us: no iframes anywhere in the app, so this is
// free, and both spellings because the old one is what some scanners
// and some older browsers read.
{ key: "Content-Security-Policy", value: "frame-ancestors 'none'" },
{ key: "X-Frame-Options", value: "DENY" },
// An uploaded photo is served through our own routes. A response
// sniffed as HTML is how an image becomes a script.
{ key: "X-Content-Type-Options", value: "nosniff" },
// A group invite link is a secret in a URL. Do not send the path to
// another origin.
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
// The camera is the app's own, on the check-in screen. Everything
// else is off, including for anything the app ever embeds.
{
key: "Permissions-Policy",
value: "camera=(self), microphone=(), geolocation=(), payment=(), usb=()",
},
// Honoured over HTTPS only, so localhost is unaffected.
{
key: "Strict-Transport-Security",
value: "max-age=31536000; includeSubDomains",
},
],
},
]),
};
export default nextConfig;