diff --git a/apps/server/src/wsServer.test.ts b/apps/server/src/wsServer.test.ts index f12792a318..573fa77eb9 100644 --- a/apps/server/src/wsServer.test.ts +++ b/apps/server/src/wsServer.test.ts @@ -648,6 +648,24 @@ describe("WebSocket Server", () => { expect(await response.text()).toContain("static-root"); }); + it("serves immutable cache headers for static asset files", async () => { + const stateDir = makeTempDir("t3code-state-static-assets-"); + const staticDir = makeTempDir("t3code-static-assets-"); + const assetsDir = path.join(staticDir, "assets"); + fs.mkdirSync(assetsDir, { recursive: true }); + fs.writeFileSync(path.join(staticDir, "index.html"), "

static-root

", "utf8"); + fs.writeFileSync(path.join(assetsDir, "index-abc123.js"), "console.log('hello');", "utf8"); + + server = await createTestServer({ cwd: "/test/project", stateDir, staticDir }); + const addr = server.address(); + const port = typeof addr === "object" && addr !== null ? addr.port : 0; + expect(port).toBeGreaterThan(0); + + const response = await fetch(`http://127.0.0.1:${port}/assets/index-abc123.js`); + expect(response.status).toBe(200); + expect(response.headers.get("cache-control")).toBe("public, max-age=31536000, immutable"); + }); + it("rejects static path traversal attempts", async () => { const stateDir = makeTempDir("t3code-state-static-traversal-"); const staticDir = makeTempDir("t3code-static-traversal-"); diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts index 2e6ac51b7f..1870e83364 100644 --- a/apps/server/src/wsServer.ts +++ b/apps/server/src/wsServer.ts @@ -565,7 +565,12 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< respond(500, { "Content-Type": "text/plain" }, "Internal Server Error"); return; } - respond(200, { "Content-Type": contentType }, data); + const headers: Record = { "Content-Type": contentType }; + const normalizedStaticRelativePath = staticRelativePath.replace(/\\/g, "/"); + if (normalizedStaticRelativePath.startsWith("assets/")) { + headers["Cache-Control"] = "public, max-age=31536000, immutable"; + } + respond(200, headers, data); }), ).catch(() => { if (!res.headersSent) {