The CSS import error was caused by richtext-lexical being in serverExternalPackages — Node can't require .css files. Removing it lets Turbopack bundle it (handling CSS natively). Other payload packages stay external (they don't import CSS). Restores turbopack as the default production bundler. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
136 lines
3.3 KiB
TypeScript
136 lines
3.3 KiB
TypeScript
import type { NextConfig } from "next";
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const { withPayload } = require("@payloadcms/next/withPayload");
|
|
|
|
import env from "./env.config";
|
|
|
|
const INTERNAL_PACKAGES = [
|
|
"@turbostarter/analytics-web",
|
|
"@turbostarter/api",
|
|
"@turbostarter/auth",
|
|
"@turbostarter/billing",
|
|
"@turbostarter/cms",
|
|
"@turbostarter/email",
|
|
"@turbostarter/db",
|
|
"@turbostarter/i18n",
|
|
"@turbostarter/monitoring-web",
|
|
"@turbostarter/shared",
|
|
"@turbostarter/storage",
|
|
"@turbostarter/ui",
|
|
"@turbostarter/ui-web",
|
|
];
|
|
|
|
// Security headers for production
|
|
const securityHeaders = [
|
|
{
|
|
key: "X-DNS-Prefetch-Control",
|
|
value: "on",
|
|
},
|
|
{
|
|
key: "Strict-Transport-Security",
|
|
value: "max-age=63072000; includeSubDomains; preload",
|
|
},
|
|
{
|
|
key: "X-Frame-Options",
|
|
value: "SAMEORIGIN",
|
|
},
|
|
{
|
|
key: "X-Content-Type-Options",
|
|
value: "nosniff",
|
|
},
|
|
{
|
|
key: "X-XSS-Protection",
|
|
value: "1; mode=block",
|
|
},
|
|
{
|
|
key: "Referrer-Policy",
|
|
value: "strict-origin-when-cross-origin",
|
|
},
|
|
{
|
|
key: "Permissions-Policy",
|
|
value: "camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
|
},
|
|
// Content-Security-Policy - configured for development flexibility
|
|
// In production, tighten 'unsafe-inline' and 'unsafe-eval' as needed
|
|
{
|
|
key: "Content-Security-Policy",
|
|
value: [
|
|
"default-src 'self'",
|
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com https://challenges.cloudflare.com",
|
|
"style-src 'self' 'unsafe-inline'",
|
|
"img-src 'self' data: blob: https: http:",
|
|
"font-src 'self' data:",
|
|
"connect-src 'self' https://api.stripe.com https://*.sentry.io wss:",
|
|
"frame-src 'self' https://js.stripe.com https://hooks.stripe.com https://challenges.cloudflare.com",
|
|
"object-src 'none'",
|
|
"base-uri 'self'",
|
|
"form-action 'self'",
|
|
"frame-ancestors 'self'",
|
|
"upgrade-insecure-requests",
|
|
].join("; "),
|
|
},
|
|
];
|
|
|
|
const config: NextConfig = {
|
|
reactStrictMode: true,
|
|
output: "standalone",
|
|
// TEMPORARY: Hono RPC + TanStack Query type inference whack-a-mole blocking production deploy.
|
|
// Ship now, fix types post-launch as dedicated tech-debt sprint.
|
|
typescript: {
|
|
ignoreBuildErrors: true,
|
|
},
|
|
serverExternalPackages: [
|
|
"better-sqlite3",
|
|
"@mapbox/node-pre-gyp",
|
|
"esbuild",
|
|
"payload",
|
|
"@payloadcms/db-postgres",
|
|
"@payloadcms/db-sqlite",
|
|
"sharp",
|
|
],
|
|
turbopack: {
|
|
rules: {
|
|
"*.svg": {
|
|
loaders: ["@svgr/webpack"],
|
|
as: "*.js",
|
|
},
|
|
},
|
|
},
|
|
|
|
images: {
|
|
remotePatterns: [
|
|
{
|
|
hostname: "images.unsplash.com",
|
|
},
|
|
],
|
|
},
|
|
|
|
/** Enables hot reloading for local packages without a build step */
|
|
transpilePackages: INTERNAL_PACKAGES,
|
|
experimental: {
|
|
optimizePackageImports: INTERNAL_PACKAGES,
|
|
},
|
|
|
|
// Apply security headers only in production
|
|
// CSP with upgrade-insecure-requests breaks Next.js client-side navigation in dev
|
|
async headers() {
|
|
if (process.env.NODE_ENV !== "production") {
|
|
return [];
|
|
}
|
|
return [
|
|
{
|
|
// Apply to all routes
|
|
source: "/:path*",
|
|
headers: securityHeaders,
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
|
enabled: env.ANALYZE,
|
|
});
|
|
|
|
export default withPayload(withBundleAnalyzer(config));
|