diff --git a/apps/server/src/projectFaviconRoute.test.ts b/apps/server/src/projectFaviconRoute.test.ts index e8b826d7be74c5d81d3b2b73d29a5d85db51bb0f..34a430d9742f7d020dc19d2939938aef09523668 100644 --- a/apps/server/src/projectFaviconRoute.test.ts +++ b/apps/server/src/projectFaviconRoute.test.ts @@ -152,4 +152,16 @@ describe("tryHandleProjectFaviconRequest", () => { expect(response.body).toBe("brand-obj-order"); }); }); + + it("serves a fallback favicon when no icon exists", async () => { + const projectDir = makeTempDir("t3code-favicon-route-fallback-"); + + await withRouteServer(async (baseUrl) => { + const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; + const response = await request(baseUrl, pathname); + expect(response.statusCode).toBe(200); + expect(response.contentType).toContain("image/svg+xml"); + expect(response.body).toContain('data-fallback="project-favicon"'); + }); + }); }); diff --git a/apps/server/src/projectFaviconRoute.ts b/apps/server/src/projectFaviconRoute.ts index 4d66f0e86f3156ef6c7f194969acbab0b0ac2ea5..cf234ad894ac94ae702cbdf317707b80d261eef2 100644 --- a/apps/server/src/projectFaviconRoute.ts +++ b/apps/server/src/projectFaviconRoute.ts @@ -9,6 +9,8 @@ const FAVICON_MIME_TYPES: Record = { ".ico": "image/x-icon", }; +const FALLBACK_FAVICON_SVG = ``; + // Well-known favicon paths checked in order. const FAVICON_CANDIDATES = [ "favicon.svg", @@ -85,6 +87,14 @@ function serveFaviconFile(filePath: string, res: http.ServerResponse): void { }); } +function serveFallbackFavicon(res: http.ServerResponse): void { + res.writeHead(200, { + "Content-Type": "image/svg+xml", + "Cache-Control": "public, max-age=3600", + }); + res.end(FALLBACK_FAVICON_SVG); +} + export function tryHandleProjectFaviconRequest(url: URL, res: http.ServerResponse): boolean { if (url.pathname !== "/api/project-favicon") { return false; @@ -118,8 +128,7 @@ export function tryHandleProjectFaviconRequest(url: URL, res: http.ServerRespons const trySourceFiles = (index: number): void => { if (index >= ICON_SOURCE_FILES.length) { - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("No favicon found"); + serveFallbackFavicon(res); return; } const sourceFile = path.join(projectCwd, ICON_SOURCE_FILES[index]!);