Files
hanzo.ai/next.config.mjs

113 lines
5.3 KiB
JavaScript

import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs'
// The build id is written into every document and names the route manifests
// (_next/static/<id>/_buildManifest.js, _ssgManifest.js). It is derived from what
// decides the runtime (the resolved dependencies and this file), so a build that
// changes a page keeps it and an incremental publish sends only the pages that
// changed instead of every document.
//
// The rule that makes that safe: the two manifests answer `no-cache` at the edge
// (universe charts/app/values/hanzo/static-sites.yaml, the *-manifest routes), so
// a stable URL is always revalidated; everything else under _next/static is
// content-hashed and immutable. Both halves must hold. A host that serves the
// manifests with a long TTL keeps an old manifest beside new HTML, and chunks then
// load as "<n>-undefined.js".
const buildId = createHash('sha256')
.update(readFileSync(new URL('./pnpm-lock.yaml', import.meta.url)))
.update(readFileSync(new URL(import.meta.url)))
.digest('hex')
.slice(0, 12)
const securityHeaders = [
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
// `microphone=(self)`: the composer's dictation button is ours.
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(self), geolocation=()' },
]
// The deep links the build declares (public/_fallback.json, exported beside the
// pages): a path under a declared prefix that no page answers renders that
// prefix's page, as the site edge (hanzo-inc/cloud apps/sites) and
// scripts/serve.mjs answer the same file. Dev reads it here so all three agree.
export function deepLinks(table = JSON.parse(readFileSync(new URL('./public/_fallback.json', import.meta.url), 'utf8'))) {
return Object.entries(table).map(([prefix, page]) => ({
source: `${prefix.replace(/\/+$/, '')}/:rest*`,
destination: `/${page.replace(/^\/+/, '').replace(/\.html?$/, '')}`,
}))
}
const nextConfig = {
output: 'export',
allowedDevOrigins: ['10.0.0.24', '10.0.0.*', 'localhost', '127.0.0.1'],
// An export is files, not a server: nothing reads the traces that cost 45 s.
outputFileTracing: false,
generateBuildId: async () => buildId,
// Types are checked once, by `pnpm typecheck`.
typescript: {
ignoreBuildErrors: true,
},
// Dev only: `output: 'export'` has no server, so the shipped site talks to
// api.hanzo.ai directly. Proxying keeps localhost reads same-origin, where
// CORS does not apply. Point HANZO_API_ORIGIN at the cloud host in .env.local
// and verify with a route only the host serves, such as /v1/templates.
async rewrites() {
const api = process.env.HANZO_API_ORIGIN || 'http://127.0.0.1:8080'
const s3 = process.env.HANZO_S3_ORIGIN || 'http://127.0.0.1:9005'
return {
afterFiles: [
{ source: '/pay/:path*', destination: 'https://hanzo.ai/pay/:path*' },
{ source: '/v1/s3/:path*', destination: `${s3}/v1/s3/:path*` },
{ source: '/v1/:path*', destination: `${api}/v1/:path*` },
{ source: '/mcp', destination: `${api}/mcp` },
{ source: '/mcp/:path*', destination: `${api}/mcp/:path*` },
{ source: '/graphql', destination: `${api}/graphql` },
],
// Only once no page and no file answered: a declared deep link.
fallback: deepLinks(),
}
},
// Next's dev badge lands on the org column.
devIndicators: false,
// TypeScript 7 has no compiler API; Next shells out to tsc instead.
experimental: {
useTypeScriptCli: true,
// @hanzo/ui imports ten brand icons from this barrel, and webpack compiled
// all 3,454 of them for both the server and the browser: 6,908 of the
// build's 18,972 modules. Rewritten to direct imports, the scripts the
// export ships are byte-identical.
optimizePackageImports: ['@icons-pack/react-simple-icons'],
},
poweredByHeader: false,
// The gui packages ship untranspiled ESM resolved against react-native.
//
// react-native-svg is here for its resolution, not its syntax. It ships a web
// build beside the native one (ReactNativeSVG.web.js, elements.web.js), chosen
// only by `.web` coming first among the extensions. Turbopack applies
// `resolveExtensions` to the project and these packages and never to the rest
// of node_modules, so outside this list it bundled the native build: Flow
// source from react-native and TurboModuleRegistry, which react-native-web
// does not export.
transpilePackages: ['@hanzo/gui', '@hanzo/ui', '@hanzo/data', '@hanzo/usage', '@hanzo/tabs', 'react-native-web', 'react-native-svg'],
turbopack: {
// Exact names: a subpath such as react-native/Libraries/... is not rewritten.
resolveAlias: {
'react-native': 'react-native-web',
// react-native-svg reads the asset registry; the web one lives here.
'@react-native/assets-registry/registry': 'react-native-web/dist/modules/AssetRegistry',
},
// The web build of a file first, then Turbopack's own list.
resolveExtensions: ['.web.tsx', '.web.ts', '.web.jsx', '.web.js', '.tsx', '.ts', '.jsx', '.js', '.mjs', '.json'],
},
images: {
unoptimized: true,
},
trailingSlash: false,
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }]
},
}
export default nextConfig