Cloudflare has strengthened protection on these hotels. Puppeteer-based bypass returns HTML instead of JSON. cloudscraper has dependency issues with Node.js v26. Reverted to simple HTTP fetch with clear error messages.
89 lines
2.3 KiB
TypeScript
89 lines
2.3 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import withBundleAnalyzer from "@next/bundle-analyzer";
|
|
import type { NextConfig } from "next";
|
|
import createNextIntlPlugin from "next-intl/plugin";
|
|
|
|
function resolveDeploymentId(): string | undefined {
|
|
const configuredId = process.env.NEXT_DEPLOYMENT_ID?.trim();
|
|
if (configuredId) return configuredId;
|
|
|
|
try {
|
|
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "ignore"],
|
|
}).trim();
|
|
} catch {
|
|
return process.env.APP_VERSION?.trim() || undefined;
|
|
}
|
|
}
|
|
|
|
const securityHeaders = [
|
|
{ key: "X-DNS-Prefetch-Control", value: "on" },
|
|
{
|
|
key: "Strict-Transport-Security",
|
|
value: "max-age=63072000; includeSubDomains; preload",
|
|
},
|
|
{ key: "X-Frame-Options", value: "DENY" },
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
{
|
|
key: "Permissions-Policy",
|
|
value: "camera=(), microphone=(), geolocation=(), interest-cohort=()",
|
|
},
|
|
// CSP is set per-request in src/proxy.ts with a script nonce (no 'unsafe-inline' for scripts).
|
|
];
|
|
|
|
const nextConfig: NextConfig = {
|
|
deploymentId: resolveDeploymentId(),
|
|
turbopack: {},
|
|
serverExternalPackages: ["mariadb", "lzma", "sharp", "pino", "pino-pretty"],
|
|
|
|
// Enable React Compiler for automatic memoization
|
|
reactCompiler: true,
|
|
|
|
// Compress responses with gzip/brotli
|
|
compress: true,
|
|
|
|
// Disable Next.js telemetry and browser sourcemaps in production
|
|
productionBrowserSourceMaps: false,
|
|
|
|
experimental: {
|
|
useTypeScriptCli: true,
|
|
},
|
|
|
|
// Add caching headers for static assets
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: "/(.*)",
|
|
headers: securityHeaders,
|
|
},
|
|
{
|
|
source: "/assets/(.*)",
|
|
headers: [
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=31536000, immutable",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
source: "/images/(.*)",
|
|
headers: [{ key: "Cache-Control", value: "public, max-age=86400" }],
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
// next-intl WITHOUT i18n routing — locale comes from the NEXT_LOCALE cookie via
|
|
// src/i18n/request.ts, so URLs and the access-guard middleware stay unchanged.
|
|
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
|
|
|
const config = withNextIntl(nextConfig);
|
|
|
|
const withBA = withBundleAnalyzer({
|
|
enabled: process.env.ANALYZE === "true",
|
|
});
|
|
|
|
export default withBA(config);
|