From 626344cc1625193734f92e9b527e7570fe96b34f Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sun, 3 May 2026 10:26:09 +0800 Subject: [PATCH] fix(web): unblock PWA icon under trailingSlash routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit next.config.ts has trailingSlash: true, so Next.js 308-redirects /icon to /icon/. The middleware matcher only excluded the no-slash form, so after the redirect the auth gate kicked in and bounced /icon/ to /cm-auth — the browser got an HTML page where it expected a PNG, and the manifest icon failed to install ('Download error or resource isn't a valid image'). - middleware: matcher now allows the optional slash on icon and apple-icon. - manifest: point icons at the canonical /icon/ and /apple-icon/ URLs so the browser fetches the PNG directly without a redirect round-trip. --- web/app/manifest.ts | 8 ++++++-- web/middleware.ts | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/web/app/manifest.ts b/web/app/manifest.ts index 107e4f0..49df23e 100644 --- a/web/app/manifest.ts +++ b/web/app/manifest.ts @@ -11,8 +11,12 @@ export default function manifest(): MetadataRoute.Manifest { background_color: "#fafafa", theme_color: "#18181b", icons: [ - { src: "/icon", sizes: "any", type: "image/png" }, - { src: "/apple-icon", sizes: "180x180", type: "image/png" }, + // Trailing slash on /icon/ and /apple-icon/ matches the canonical URL + // Next.js serves under `trailingSlash: true`. Without the slash the + // browser would hit a 308 redirect, then the gated /icon/ path, then + // get HTML back instead of the PNG. + { src: "/icon/", sizes: "any", type: "image/png" }, + { src: "/apple-icon/", sizes: "180x180", type: "image/png" }, ], }; } diff --git a/web/middleware.ts b/web/middleware.ts index f27cde4..2378817 100644 --- a/web/middleware.ts +++ b/web/middleware.ts @@ -36,7 +36,12 @@ export async function middleware(req: NextRequest) { } export const config = { + // next.config.ts sets `trailingSlash: true`, so /icon redirects to /icon/. + // The icon$/apple-icon$ alternatives below allow the optional slash so the + // canonical (slashed) URL bypasses the auth gate too — otherwise the + // browser hits the redirect, follows it to the slashed form, and the gate + // refuses to serve the image and bounces to /cm-auth. matcher: [ - "/((?!_next|icon$|apple-icon$|manifest.webmanifest|favicon.ico).*)", + "/((?!_next|icon/?$|apple-icon/?$|manifest.webmanifest|favicon.ico).*)", ], };