From 32af5efee960c02ed4f387125cbc8ca7563a32b5 Mon Sep 17 00:00:00 2001 From: adrians5j Date: Wed, 29 Jul 2026 14:20:57 +0200 Subject: [PATCH] fix: look up redirects against the API instead of fetching our own route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Middleware fetched its own /api/redirects route, which meant a second request to this deployment on every page view — and it failed in two environments, silently. Behind a local HTTPS proxy the Edge runtime doesn't trust the proxy's certificate, so the request threw SELF_SIGNED_CERT_IN_CHAIN. On a deployment protected by Vercel Authentication the self-request carries no credentials and is answered with a 401 or an auth redirect rather than by our route. In both cases the surrounding `catch {}` discarded the error, so a broken lookup was indistinguishable from "no redirect is configured" and every redirect quietly stopped working. Query the Website Builder API directly instead. It's the only host we need, it has a real certificate and its own authentication, and it drops a hop: middleware -> API rather than middleware -> route -> API. Also removes a function invocation per request. The response shape is declared locally rather than imported, so middleware stays free of runtime dependencies. Failures are now logged. A failed lookup still doesn't take the page down, but it's no longer invisible. Verified: /a redirects to /b over both plain http://localhost:3000 and https://website-builder-nextjs.localhost (the case that was broken), and a path with no redirect passes through untouched. Co-Authored-By: Claude Opus 5 (1M context) --- src/middleware.ts | 54 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 0572da3..13421b9 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -3,6 +3,19 @@ import { NextResponse, type NextRequest } from "next/server"; const ENABLE_DRAFT_MODE_ROUTE = "/api/preview"; +/** + * Shape of a single entry returned by the Website Builder's `GET /wb/redirects` endpoint. + * Declared locally on purpose: middleware runs on every request, so it stays free of runtime + * dependencies, and `@webiny/website-builder-sdk` (where this type lives) is not a direct + * dependency of this project. + */ +interface PublicRedirect { + id: string; + from: string; + to: string; + permanent: boolean; +} + export async function middleware(request: NextRequest) { const { searchParams, pathname } = request.nextUrl; // Check if the preview/editing flag is set. @@ -57,23 +70,46 @@ export async function middleware(request: NextRequest) { } // Check if there's a redirect defined for the requested page. - const redirectsUrl = new URL( - `/api/redirects?wb.tenant=${tenantId}&pathname=${encodeURIComponent(pathname)}`, - request.url, - ); - + // + // This queries the Website Builder API directly rather than fetching our own /api/redirects + // route. Fetching our own origin from middleware costs a second function invocation and a full + // network round trip on every request, and it breaks in ways that are hard to see: + // + // - Behind a local HTTPS proxy the certificate isn't trusted by the Edge runtime, so the + // request throws (SELF_SIGNED_CERT_IN_CHAIN) and every redirect silently stops working. + // - On a deployment protected by Vercel Authentication, the self-request carries no + // credentials and is answered with a 401 or an auth redirect instead of our route. + // + // Talking to the API directly avoids both: it is the only host we need to reach, and it has a + // real certificate and its own authentication. try { - const redirectResponse = await fetch(redirectsUrl); + const response = await fetch( + `${process.env.NEXT_PUBLIC_WEBSITE_BUILDER_API_HOST}/wb/redirects`, + { + headers: { + "X-Tenant": tenantId, + Authorization: `Bearer ${process.env.NEXT_PUBLIC_WEBSITE_BUILDER_API_KEY}`, + }, + }, + ); + + if (!response.ok) { + throw new Error(`Redirects lookup responded with ${response.status}.`); + } + + const redirects: PublicRedirect[] = await response.json(); + const redirect = redirects.find((item) => item.from === pathname); - const { redirect } = await redirectResponse.json(); if (redirect) { return NextResponse.redirect( new URL(redirect.to, request.url), redirect.permanent ? 308 : 307, ); } - } catch { - // Do nothing. Most probably redirect was simply not found. + } catch (err) { + // A failed lookup must not take the page down, but it must not be silent either: swallowing it + // is indistinguishable from "no redirect is configured" and hides real API failures. + console.error(`[middleware] Redirect lookup failed for "${pathname}":`, err); } // For all other requests, continue as normal without any modifications.