From b88432fcf4c030ab582f005e4d29a80a1416509d Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Sat, 19 Apr 2025 19:44:27 +0200 Subject: [PATCH] Change Docker handling of custom rewrites --- Dockerfile | 3 +- docker/middleware.js | 60 --------------------------- package.json | 3 +- scripts/set-routes-manifest.js | 74 ++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 62 deletions(-) delete mode 100644 docker/middleware.js create mode 100644 scripts/set-routes-manifest.js diff --git a/Dockerfile b/Dockerfile index f9485152..2e4fdcdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,6 @@ FROM node:22-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -COPY docker/middleware.js ./src ARG DATABASE_TYPE ARG BASE_PATH @@ -51,6 +50,8 @@ COPY --from=builder /app/scripts ./scripts COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +RUN mv ./.next/routes-manifest.json ./.next/routes-manifest-orig.json + USER nextjs EXPOSE 3000 diff --git a/docker/middleware.js b/docker/middleware.js deleted file mode 100644 index 75a74e02..00000000 --- a/docker/middleware.js +++ /dev/null @@ -1,60 +0,0 @@ -import { NextResponse } from 'next/server'; - -export const config = { - matcher: '/:path*', -}; - -const apiHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*', - 'Access-Control-Allow-Methods': 'GET, DELETE, POST, PUT', - 'Access-Control-Max-Age': process.env.CORS_MAX_AGE || '86400', - 'Cache-Control': 'no-cache', -}; - -const trackerHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Cache-Control': 'public, max-age=86400, must-revalidate', -}; - -function customCollectEndpoint(req) { - const collectEndpoint = process.env.COLLECT_API_ENDPOINT; - - if (collectEndpoint) { - const url = req.nextUrl.clone(); - const { pathname } = url; - - if (pathname.endsWith(collectEndpoint)) { - url.pathname = '/api/send'; - return NextResponse.rewrite(url, { headers: apiHeaders }); - } - } -} - -function customScriptName(req) { - const scriptName = process.env.TRACKER_SCRIPT_NAME; - - if (scriptName) { - const url = req.nextUrl.clone(); - const { pathname } = url; - const names = scriptName.split(',').map(name => name.trim().replace(/^\/+/, '')); - - if (names.find(name => pathname.endsWith(name))) { - url.pathname = '/script.js'; - return NextResponse.rewrite(url, { headers: trackerHeaders }); - } - } -} - -export default function middleware(req) { - const fns = [customCollectEndpoint, customScriptName]; - - for (const fn of fns) { - const res = fn(req); - if (res) { - return res; - } - } - - return NextResponse.next(); -} diff --git a/package.json b/package.json index e630e407..7bc766c7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build": "npm-run-all check-env build-db check-db build-tracker build-geo build-app", "start": "next start", "build-docker": "npm-run-all build-db build-tracker build-geo build-app", - "start-docker": "npm-run-all check-db update-tracker start-server", + "start-docker": "npm-run-all check-db update-tracker set-routes-manifest start-server", "start-env": "node scripts/start-env.js", "start-server": "node server.js", "build-app": "next build", @@ -25,6 +25,7 @@ "build-geo": "node scripts/build-geo.js", "build-db-schema": "prisma db pull", "build-db-client": "prisma generate", + "set-routes-manifest": "node scripts/set-routes-manifest.js", "update-tracker": "node scripts/update-tracker.js", "update-db": "prisma migrate deploy", "check-db": "node scripts/check-db.js", diff --git a/scripts/set-routes-manifest.js b/scripts/set-routes-manifest.js new file mode 100644 index 00000000..e28cdcaf --- /dev/null +++ b/scripts/set-routes-manifest.js @@ -0,0 +1,74 @@ +/* eslint-disable no-console */ +require('dotenv').config(); +const fs = require('fs'); +const path = require('path'); + +const routesManifestPath = path.resolve(__dirname, '../.next/routes-manifest.json'); +const originalPath = path.resolve(__dirname, '../.next/routes-manifest-orig.json'); +const originalManifest = require(originalPath); + +const API_PATH = '/api/:path*'; +const TRACKER_SCRIPT = '/script.js'; + +const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT; +const trackerScriptName = process.env.TRACKER_SCRIPT_NAME; + +const headers = []; +const rewrites = []; + +if (collectApiEndpoint) { + const apiRoute = originalManifest.headers.find((route) => route.source === API_PATH); + const routeRegex = new RegExp(apiRoute.regex); + + rewrites.push({ + source: collectApiEndpoint, + destination: '/api/send', + }); + + if (!routeRegex.test(collectApiEndpoint)) { + headers.push({ + source: collectApiEndpoint, + headers: apiRoute.headers, + }); + } +} + +if (trackerScriptName) { + const trackerRoute = originalManifest.headers.find((route) => route.source === TRACKER_SCRIPT); + + const names = trackerScriptName?.split(',').map(name => name.trim()); + + if (names) { + names.forEach(name => { + const normalizedSource = `/${name.replace(/^\/+/, '')}`; + + rewrites.push({ + source: normalizedSource, + destination: TRACKER_SCRIPT, + }); + + headers.push({ + source: normalizedSource, + headers: trackerRoute.headers, + }); + }); + } +} + +const routesManifest = { ...originalManifest }; + +if (rewrites.length != 0) { + const { buildCustomRoute } = require('next/dist/lib/build-custom-route'); + + const builtHeaders = headers.map((header) => buildCustomRoute('header', header)); + const builtRewrites = rewrites.map((rewrite) => buildCustomRoute('rewrite', rewrite)); + + routesManifest.headers = [...originalManifest.headers, ...builtHeaders]; + routesManifest.rewrites = [...builtRewrites, ...originalManifest.rewrites]; + + console.log('Using updated Next.js routes manifest'); +} else { + console.log('Using original Next.js routes manifest'); +} + +fs.writeFileSync(routesManifestPath, JSON.stringify(routesManifest, null, 2));