Repository navigation
Expand file tree
/
Copy pathnext.config.ts
More file actions
278 lines (251 loc) · 7.95 KB
/
Copy pathnext.config.ts
File metadata and controls
278 lines (251 loc) · 7.95 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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
import { ChildProcess, spawn } from 'node:child_process'
import process from 'node:process'
import { NextConfig } from 'next'
import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'
import { withPayload } from '@payloadcms/next/withPayload'
import z from 'zod'
import { envSchema } from '@/types/environment'
let server: ChildProcess | null = null
const createTunnel = (token: string) =>
new Promise<ChildProcess | null>((resolve) => {
// No `shell: true`: the token is passed as its own argv entry rather than
// concatenated into a command string, which also silences DEP0190.
const childProcess = spawn('npx', ['wrangler', 'tunnel', 'run', '--token', token], {
stdio: 'ignore',
})
/**
* Reap the tunnel with the dev server. Without this every `next dev`
* leaves a live `wrangler tunnel run` behind, and they accumulate
* silently across restarts.
*
* Only `exit` is hooked: scripts/dev.mjs already forwards SIGINT/SIGTERM
* to this process and re-raises them, so adding handlers here would
* replace Node's default signal behaviour and let the wrapper's own
* exit-code handling be pre-empted by a hardcoded `process.exit(0)`.
*/
process.once('exit', () => {
if (!childProcess.killed) {
childProcess.kill('SIGTERM')
}
})
/**
* `spawn` fires for npx itself, so it only proves the launcher started —
* wrangler may still fail afterwards (missing binary, rejected token).
* An early exit is therefore treated as a failed tunnel, and whichever
* of the two settles first wins.
*/
childProcess.once('spawn', () => {
resolve(childProcess)
})
childProcess.once('error', () => {
resolve(null)
})
childProcess.once('exit', (code) => {
if (code !== 0) resolve(null)
})
})
export default async (phase, { defaultConfig: _defaultConfig }) => {
/**
* The tunnel is opt-in via `bun run dev --tunnel`, which scripts/dev.mjs
* translates into DEV_TUNNEL=1. Having the credentials in .env.local is
* no longer enough to start it — otherwise every `next dev` opens a
* public tunnel as a side effect of the file being present.
*
* This runs before validation so the addresses it swaps in are the ones
* the schema checks.
*/
const tunnelRequested = phase === PHASE_DEVELOPMENT_SERVER && process.env.DEV_TUNNEL === '1'
if (tunnelRequested) {
const { CLOUDFLARE_TUNNEL_URL, CLOUDFLARE_TUNNEL_HOST, CLOUDFLARE_TUNNEL_TOKEN } = process.env
if (!CLOUDFLARE_TUNNEL_URL || !CLOUDFLARE_TUNNEL_HOST || !CLOUDFLARE_TUNNEL_TOKEN) {
console.error(
'\n[tunnel] --tunnel needs CLOUDFLARE_TUNNEL_URL, CLOUDFLARE_TUNNEL_HOST and CLOUDFLARE_TUNNEL_TOKEN\n',
)
process.exit(1)
}
process.env.SERVER_HOST = CLOUDFLARE_TUNNEL_HOST
process.env.SERVER_URL = CLOUDFLARE_TUNNEL_URL
}
/* Environment validation */
const parsedEnv = envSchema.safeParse(process.env)
if (!parsedEnv.success) {
console.error(`\n${z.prettifyError(parsedEnv.error)}\n`)
process.exit(1)
}
if (tunnelRequested) {
// Next reloads this config on edit; the tunnel outlives those reloads.
if (!server) {
server = await createTunnel(process.env.CLOUDFLARE_TUNNEL_TOKEN)
if (!server) {
console.error('\n[tunnel] failed to start `wrangler tunnel run` — is wrangler installed?\n')
process.exit(1)
}
}
/**
* Only this process has the tunnel URL — .env.local is loaded by Next,
* not by scripts/dev.mjs. Hand it to the wrapper, which splices it into
* Next's address block and swallows this marker.
*
* stderr, not stdout: Next captures stdout while the config loads, so a
* marker written there never reaches the parent.
*/
process.stderr.write(`__DEV_TUNNEL_URL__${process.env.SERVER_URL}\n`)
}
const nextConfig: NextConfig = {
poweredByHeader: false,
productionBrowserSourceMaps: false,
reactStrictMode: true,
cacheComponents: true,
partialPrefetching: true,
// The web image contains only .next/standalone (see scripts/assemble-images.mjs).
output: 'standalone',
experimental: {
turbopackServerFastRefresh: true,
serverActions: {
bodySizeLimit: '10mb',
},
optimizePackageImports: [
// 'payload',
'@payloadcms/next',
'@payloadcms/translations',
'@payloadcms/ui',
'shiki',
// 'svgo',
'usehooks-ts',
],
},
/**
* Allowed Dev Origins
*/
allowedDevOrigins: [
'localhost:3000',
'*.localhost:3000',
'daniel.heene.nexus',
'*.daniel.heene.nexus',
'daniel.heene.local',
'*.daniel.heene.local',
],
/**
* Logging
*/
logging: {
fetches: {
fullUrl: true,
hmrRefreshes: true,
},
serverFunctions: true,
incomingRequests: true,
browserToTerminal: 'error',
},
serverExternalPackages: [
// WASM decoder must not be inlined by Turbopack (invalid octal escapes
// in the generated template string break the server chunk)
'mediabunny',
'@mediabunny/server',
'@react-pdf/renderer',
'svgo',
'pdf-parse',
'node-av',
'@takumi-rs/core',
'@napi-rs/canvas',
],
turbopack: {
resolveExtensions: [
'.ts',
'.tsx',
'.js',
'.jsx',
'.mjs',
'.cjs',
'.mdx',
'.wasm',
'.json',
'.css',
'.scss',
'.svg',
],
},
/**
* Configuration object for Next.js image optimization settings.
* Defines supported image formats, responsive breakpoints, and security policies for image handling.
*/
images: {
formats: ['image/webp', 'image/avif'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
remotePatterns: [
new URL('http://localhost:3000/**'),
new URL('https://daniel.heene.io/**'),
new URL('https://daniel.heene.dev/**'),
new URL('https://daniel.heene.review/**'),
new URL('https://daniel.heene.nexus/**'),
new URL('https://daniel.heene.local/**'),
new URL('https://fsn1.your-objectstorage.com/**'),
new URL('https://nbg1.your-objectstorage.com/**'),
new URL('https://hel1.your-objectstorage.com/**'),
new URL('https://cdn.pixabay.com/**'),
new URL('https://images.unsplash.com/**'),
],
localPatterns: [
{
pathname: '**',
},
],
contentDispositionType: 'inline',
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
dangerouslyAllowSVG: true,
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
],
},
{
source: '/:path*{/}?',
headers: [
{
key: 'X-Accel-Buffering',
value: 'no',
},
],
},
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: process.env.SERVER_URL ?? '*',
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
]
},
rewrites: async () => ({
beforeFiles: [
{
source: '/stats/:match*',
destination: process.env.NEXT_PUBLIC_UMAMI_URL + '/:match*',
},
],
afterFiles: [],
fallback: [],
}),
}
return withPayload(nextConfig, {
devBundleServerPackages: false,
})
}