From 5f7cd2405a1758ef83821257a926f7e5e8e5691d Mon Sep 17 00:00:00 2001 From: Hwanseo Choi Date: Wed, 18 Mar 2026 23:53:22 +0900 Subject: [PATCH 1/2] cache static assets in server --- apps/server/src/wsServer.test.ts | 18 ++++++++++++++++++ apps/server/src/wsServer.ts | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) 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..095291bbe7 100644 --- a/apps/server/src/wsServer.ts +++ b/apps/server/src/wsServer.ts @@ -565,7 +565,11 @@ 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 }; + if (staticRelativePath.startsWith("assets/")) { + headers["Cache-Control"] = "public, max-age=31536000, immutable"; + } + respond(200, headers, data); }), ).catch(() => { if (!res.headersSent) { From c40dd7e372e96d9e0279eef98f720c74b6acf6ca Mon Sep 17 00:00:00 2001 From: Hwanseo Choi Date: Thu, 19 Mar 2026 00:21:38 +0900 Subject: [PATCH 2/2] Normalize Static Path Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- apps/server/src/wsServer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts index 095291bbe7..1870e83364 100644 --- a/apps/server/src/wsServer.ts +++ b/apps/server/src/wsServer.ts @@ -566,7 +566,8 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< return; } const headers: Record = { "Content-Type": contentType }; - if (staticRelativePath.startsWith("assets/")) { + const normalizedStaticRelativePath = staticRelativePath.replace(/\\/g, "/"); + if (normalizedStaticRelativePath.startsWith("assets/")) { headers["Cache-Control"] = "public, max-age=31536000, immutable"; } respond(200, headers, data);