forked from SkateHive/skatehive3.0
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnext.config.mjs
More file actions
390 lines (371 loc) · 14.8 KB
/
Copy pathnext.config.mjs
File metadata and controls
390 lines (371 loc) · 14.8 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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
import { fileURLToPath } from 'url';
import { dirname, resolve as pathResolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// ---------------------------------------------------------------------------
// Server-side polyfills for browser-only globals.
//
// Some dependencies (e.g. @farcaster/auth-kit → idb-keyval) access
// `indexedDB` or `localStorage` at the module/top-level. When Next.js
// bundles them into a server chunk or prerenders pages, these globals
// don't exist and the process crashes.
//
// We shim them here (before any Next.js code runs) so that the calls
// silently return empty/no-op values instead of throwing.
// ---------------------------------------------------------------------------
if (typeof globalThis.indexedDB === "undefined") {
const noop = () => {};
const noopReq = (result) => {
const r = { result, error: null, onsuccess: null, onerror: null, oncomplete: null, onupgradeneeded: null };
queueMicrotask(() => { r.onsuccess?.({ target: r }); r.oncomplete?.({ target: r }); });
return r;
};
const noopStore = () => ({
get: () => noopReq(undefined), put: () => noopReq(), delete: () => noopReq(),
getAll: () => noopReq([]), clear: () => noopReq(), count: () => noopReq(0),
openCursor: () => noopReq(null), openKeyCursor: () => noopReq(null),
});
const noopTx = () => ({ objectStore: noopStore, abort: noop, commit: noop, oncomplete: null, onerror: null, onabort: null });
const noopDB = () => ({
createObjectStore: noopStore, deleteObjectStore: noop,
transaction: noopTx, close: noop,
name: "", version: 1, objectStoreNames: { length: 0, contains: () => false },
onclose: null, onabort: null, onerror: null, onversionchange: null,
});
globalThis.indexedDB = { open: () => noopReq(noopDB()), deleteDatabase: () => noopReq(), databases: async () => [], cmp: () => 0 };
}
if (typeof globalThis.localStorage === "undefined" || typeof globalThis.localStorage?.getItem !== "function") {
const store = new Map();
globalThis.localStorage = {
getItem: (k) => store.get(k) ?? null,
setItem: (k, v) => store.set(k, String(v)),
removeItem: (k) => store.delete(k),
clear: () => store.clear(),
key: (i) => [...store.keys()][i] ?? null,
get length() { return store.size; },
};
}
/** @type {import('next').NextConfig} */
const nextConfig = {
// Production optimizations
compress: true,
poweredByHeader: false,
reactStrictMode: true,
// Performance optimizations
compiler: {
removeConsole: process.env.NODE_ENV === 'production' ? {
exclude: ['error', 'warn'],
} : false,
},
// Image optimization domains
images: {
// Allow any external domain (Next.js will optimize them)
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
{
protocol: 'http',
hostname: '**',
},
],
// Increase allowed image sizes for high-res skate photos
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// Use Next.js default loader (optimizes all images)
loader: 'default',
// Formats supported
formats: ['image/webp', 'image/avif'],
// Allow longer cache (images don't change)
minimumCacheTTL: 31536000,
},
experimental: {
serverActions: {
bodySizeLimit: '200mb', // Increase the body size limit for large video uploads
},
// Cut shared-chunk size by letting Next rewrite barrel-file imports
// into direct subpath imports. These libs all have large index.ts
// re-exports that defeat tree-shaking otherwise.
optimizePackageImports: [
'@chakra-ui/react',
'@chakra-ui/icons',
'react-icons',
'wagmi',
'viem',
'@hiveio/dhive',
'framer-motion',
'@rainbow-me/rainbowkit',
],
},
// Heavy server-only packages with WASM or native bindings — keep them
// out of the bundler entirely so they're required at runtime from
// node_modules. Also silences the libheif-js "Critical dependency"
// warning that adds compile time on every build.
serverExternalPackages: ['libheif-js', 'heic-convert', 'heic-decode', 'sharp'],
// ESM-only packages that webpack chokes on inside dynamically-imported chunks
// (e.g. @aioha/providers exposes "./react" with only an "import" condition,
// so when CoinCreationModal is loaded via next/dynamic the chunk's module
// factory comes back undefined). Transpiling lets Next normalize them.
transpilePackages: ['@aioha/react-ui', '@aioha/providers', '@aioha/aioha'],
webpack: (config, { isServer, dev, webpack }) => {
// On the server, replace idb-keyval with a no-op stub so that
// indexedDB.open() is never called. The real idb-keyval will
// still be used on the client.
if (isServer) {
config.resolve.alias = {
...config.resolve.alias,
'idb-keyval': pathResolve(__dirname, 'lib/stubs/idb-keyval-server.js'),
};
// Polyfill localStorage for prerender workers — Chakra UI's
// color-mode script accesses localStorage at module level,
// crashing static generation. BannerPlugin injects this into
// every server chunk so it runs before any Chakra code.
config.plugins.push(
new webpack.BannerPlugin({
banner: [
'if(typeof globalThis.localStorage==="undefined"||typeof globalThis.localStorage.getItem!=="function"){',
' var _ls={};',
' globalThis.localStorage={',
' getItem:function(k){return Object.prototype.hasOwnProperty.call(_ls,k)?_ls[k]:null},',
' setItem:function(k,v){_ls[k]=String(v)},',
' removeItem:function(k){delete _ls[k]},',
' clear:function(){_ls={}},',
' key:function(i){return Object.keys(_ls)[i]||null},',
' get length(){return Object.keys(_ls).length}',
' };',
'}',
].join(''),
raw: true,
entryOnly: false,
}),
);
}
if (!isServer) {
config.resolve.fallback = {
fs: false,
net: false,
tls: false,
crypto: false,
stream: false,
buffer: false,
util: false,
assert: false,
url: false,
os: false,
path: false,
memcpy: false,
'pino-pretty': false,
};
}
// Remove console statements in production
if (!dev) {
config.optimization = {
...config.optimization,
usedExports: true,
sideEffects: false,
minimize: true,
minimizer: [
...(config.optimization.minimizer || []),
].map((minimizer) => {
if (minimizer.constructor.name === 'TerserPlugin') {
return Object.assign({}, minimizer, {
options: {
...minimizer.options,
terserOptions: {
...minimizer.options?.terserOptions,
compress: {
...minimizer.options?.terserOptions?.compress,
drop_console: true,
drop_debugger: true,
},
},
},
});
}
return minimizer;
}),
};
}
// Ignore specific problematic modules
config.resolve.alias = {
...config.resolve.alias,
'memcpy': false,
'pino-pretty': false,
// Silence MetaMask SDK warning — it tries to import React Native storage
// which doesn't exist in browser. RainbowKit uses wagmi's injected connector instead.
'@react-native-async-storage/async-storage': false,
};
// Add externals for server-side only modules
if (!isServer) {
config.externals = config.externals || [];
config.externals.push({
'memcpy': 'memcpy',
'pino-pretty': 'pino-pretty',
});
}
return config;
},
async redirects() {
return [
// Legacy game route
{
source: '/game',
destination: '/games',
permanent: true,
},
// Profile redirects: /@username -> /user/username
{
source: '/@:username',
destination: '/user/:username',
permanent: true,
},
// Old profile page: /skater/author -> /user/author
{
source: '/skater/:author',
destination: '/user/:author',
permanent: true,
},
// Post redirects: /@author/permlink -> /post/author/permlink
{
source: '/@:author/:permlink',
destination: '/post/:author/:permlink',
permanent: true,
},
// Category post redirects: /category/@author/permlink -> /post/author/permlink
{
source: '/:category/@:author/:permlink',
destination: '/post/:author/:permlink',
permanent: true,
},
{
source: '/post/hive-173115/@:author/:permlink',
destination: '/post/:author/:permlink',
permanent: true,
},
// Redirect /skatespots to /map
{
source: '/skatespots',
destination: '/map',
permanent: true,
},
// Hive community URL patterns (from Ecency/PeakD links)
{
source: '/hive-173115/@:author/:permlink',
destination: '/post/:author/:permlink',
permanent: true,
},
// Redirect /blog/tag URLs with encoded # (from old sitemap)
{
source: '/blog/tag/%23:tag',
destination: '/blog/tag/:tag',
permanent: true,
}
];
},
async headers() {
// Block search engine indexing on non-production deployments
// (dev.skatehive.app, preview branches, etc.) to avoid duplicate content
const isProduction = process.env.VERCEL_ENV === 'production';
const noIndexHeaders = !isProduction ? [
{
key: 'X-Robots-Tag',
value: 'noindex, nofollow',
},
] : [];
return [
{
// Apply headers to all routes
source: '/(.*)',
headers: [
...noIndexHeaders,
{
// Allow embedding in any iframe (for Farcaster frames and embeds)
key: 'X-Frame-Options',
value: 'ALLOWALL',
},
{
// Alternative modern approach - allow all origins to embed
key: 'Content-Security-Policy',
value: "frame-ancestors *;",
},
{
// Prevent MIME type sniffing
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
// Enable XSS protection
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
// Referrer policy for privacy
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
// Permissions policy
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(self)',
},
{
// Strict Transport Security (force HTTPS)
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload',
},
],
},
{
// Cache static assets (images, fonts) for 1 year
source: '/(.*)\\.(jpg|jpeg|png|gif|webp|svg|woff|woff2|ttf|eot|ico)$',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
// Cache JS and CSS chunks for 1 year (Next.js adds hash to filenames)
source: '/_next/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
// Build manifests are not content-hashed — bust cache on every deploy
source: '/_next/static/:buildId/_buildManifest.js',
headers: [
{
key: 'Cache-Control',
value: 'no-cache, no-store, must-revalidate',
},
],
},
{
source: '/_next/static/:buildId/_ssgManifest.js',
headers: [
{
key: 'Cache-Control',
value: 'no-cache, no-store, must-revalidate',
},
],
},
{
// Cache public folder assets for 1 day
source: '/public/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=86400, stale-while-revalidate=604800',
},
],
},
];
},
}
export default nextConfig;