-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathnext.config.ts
More file actions
120 lines (113 loc) · 4.49 KB
/
Copy pathnext.config.ts
File metadata and controls
120 lines (113 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import path from "node:path";
import type { NextConfig } from "next";
import { headerRules } from "./lib/security/headers";
const isCloudflare = process.env.DEPLOY_TARGET === "cloudflare";
const PROJECT_ROOT = process.cwd();
const DRIVER_TARGET = isCloudflare ? "workers" : "node";
const TARGET_FILES = [
["lib/db/_driver", `lib/db/_driver.${DRIVER_TARGET}`],
["lib/db/request-scope", `lib/db/request-scope.${DRIVER_TARGET}`],
["lib/db/rls-read", `lib/db/rls-read.${DRIVER_TARGET}`],
["lib/db/_auth-kv-storage", `lib/db/_auth-kv-storage.${DRIVER_TARGET}`],
["lib/realtime/_broker", `lib/realtime/_broker.${DRIVER_TARGET}`],
["lib/email/_sender", `lib/email/_sender.${DRIVER_TARGET}`],
["lib/email/_defer", `lib/email/_defer.${DRIVER_TARGET}`],
["lib/email/_budget", `lib/email/_budget.${DRIVER_TARGET}`],
] as const;
/**
* Each alternative pins the basename to its own parent directory so an
* unrelated file with a matching basename in a sibling tree (e.g.
* `lib/realtime/_driver.ts` or `lib/db/_broker.ts`) is never silently
* aliased to the wrong sibling. Rebuilt directly from `TARGET_FILES` so
* adding a new indirection only touches that constant.
*/
const REPLACEMENT_REGEX = new RegExp(
`(^|/)(${TARGET_FILES.map(([from]) => from).join("|")})(\\.[cm]?[tj]sx?)?$`,
);
/**
* Rewrite a driver / broker indirection import to its per-target sibling.
* Anchored on the `lib/db/`, `lib/realtime/`, and `lib/email/` parents so test
* fixtures or transitive deps sharing a basename are never touched. Runs at
* module-resolution time so the unused target never enters the bundle.
*
* @param resource - Module-resolution data mutated in place by webpack.
*/
function rewriteDriverImport(resource: { request: string; context?: string }) {
const match = resource.request.match(REPLACEMENT_REGEX);
if (!match) return;
const fullPath = match[2];
const replacement = TARGET_FILES.find(([from]) => from === fullPath);
if (!replacement) return;
resource.request = path.resolve(PROJECT_ROOT, `${replacement[1]}.ts`);
}
/**
* Async factory so the OpenNext dev-mode initializer can be `await`ed
* without top-level await in the config module — Next loads the compiled
* config via `require()`, which rejects async modules.
*
* @returns Next config with `output: "standalone"` gated on
* `DEPLOY_TARGET=cloudflare` and webpack aliases pointed at the
* per-target driver / broker indirection files.
*/
async function buildNextConfig(): Promise<NextConfig> {
if (isCloudflare) {
const { initOpenNextCloudflareForDev } = await import(
"@opennextjs/cloudflare"
);
initOpenNextCloudflareForDev();
}
return {
...(isCloudflare
? {}
: { output: "standalone", outputFileTracingRoot: PROJECT_ROOT }),
poweredByHeader: false,
/**
* Surface deploy-time flags to the bundle. `NEXT_PUBLIC_*` is inlined by
* Next at build, so both the server gate and the static sign-up page read
* the same baked value. `NEXT_PUBLIC_DEPLOY_TARGET`: self-host builds get
* an empty string, Cloudflare builds get `"cloudflare"`.
* `NEXT_PUBLIC_SIGNUPS_ENABLED`: `"true"` opts a hosted deploy into open
* signup (set by `deploy:cf` and `deploy:cf:dev`); hosted deploys are
* invite-only without it.
* `NEXT_PUBLIC_TURNSTILE_SITE_KEY`: the widget's public site key. Empty
* disables the widget and keeps `frame-src 'none'`; the paired
* `TURNSTILE_SECRET_KEY` runtime secret arms the server-side plugin.
*/
env: {
NEXT_PUBLIC_DEPLOY_TARGET: process.env.DEPLOY_TARGET ?? "",
NEXT_PUBLIC_SIGNUPS_ENABLED: process.env.SIGNUPS_ENABLED ?? "",
NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.TURNSTILE_SITE_KEY ?? "",
},
experimental: {
serverActions: {
bodySizeLimit: "2mb",
},
},
webpack(
config: { plugins?: unknown[]; module: { rules: unknown[] } },
ctx: {
webpack: {
NormalModuleReplacementPlugin: new (
re: RegExp,
fn: (r: { request: string }) => void,
) => unknown;
};
},
) {
const plugins = config.plugins ?? [];
plugins.push(
new ctx.webpack.NormalModuleReplacementPlugin(
REPLACEMENT_REGEX,
rewriteDriverImport,
),
);
config.plugins = plugins;
config.module.rules.push({ test: /\.md$/, type: "asset/source" });
return config;
},
async headers() {
return headerRules(process.env.NODE_ENV === "production");
},
};
}
export default buildNextConfig;