mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 10:13:47 +10:00
perf(server): compress the web app's static files and HTML shells
Self-hosted Docker installs served the SPA's CSS, JS and HTML shells without any Content-Encoding. Hono's compress() now wraps only the web routes: it is registered after every API, MCP and upload route, so their streams are never buffered or re-encoded. The Vercel app keeps relying on its CDN, which already compresses.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
@@ -205,6 +206,32 @@ describe("createApp", () => {
|
||||
expect(mocks.handleWebApp).toHaveBeenCalledWith(request);
|
||||
expect(mocks.serveWebDistStatic).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("compresses the web app's HTML but never API streams or the Vercel app", async () => {
|
||||
const { createApp } = await import("./app");
|
||||
const html = `<!doctype html>${"<p>Reactive Resume</p>".repeat(200)}`;
|
||||
const htmlResponse = () =>
|
||||
new Response(html, {
|
||||
headers: { "Content-Type": "text/html; charset=UTF-8", "Cache-Control": "private, no-store", Vary: "Cookie" },
|
||||
});
|
||||
const stream = () => new Response("data: x\n\n".repeat(500), { headers: { "Content-Type": "application/json" } });
|
||||
mocks.handleWebApp.mockImplementation(async () => htmlResponse());
|
||||
mocks.handleRpc.mockImplementation(async () => stream());
|
||||
mocks.handleMcp.mockImplementation(async () => stream());
|
||||
const headers = { "Accept-Encoding": "br, gzip" };
|
||||
|
||||
const page = await createApp().request("http://localhost:3000/", { headers });
|
||||
const rpc = await createApp().request("http://localhost:3000/api/rpc/agent/chat", { headers });
|
||||
const mcp = await createApp().request("http://localhost:3000/mcp", { headers });
|
||||
const vercelPage = await createApp({ serveStatic: false }).request("http://localhost:3000/", { headers });
|
||||
|
||||
expect(page.headers.get("content-encoding")).toBe("gzip");
|
||||
expect(page.headers.get("vary")).toBe("Cookie, Accept-Encoding");
|
||||
expect(page.headers.get("cache-control")).toBe("private, no-store");
|
||||
expect(gunzipSync(Buffer.from(await page.arrayBuffer())).toString()).toBe(html);
|
||||
for (const response of [rpc, mcp, vercelPage]) expect(response.headers.get("content-encoding")).toBeNull();
|
||||
expect(vercelPage.headers.get("vary")).toBe("Cookie");
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["/auth/consent", "/auth/consent/", "/auth/login"])("prevents framing or caching %s", async (path) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Context } from "hono";
|
||||
import { isIP } from "node:net";
|
||||
import { getConnInfo } from "@hono/node-server/conninfo";
|
||||
import { Hono } from "hono";
|
||||
import { compress } from "hono/compress";
|
||||
import { prepareStagedBody, withStagedBody } from "@reactive-resume/api/features/storage/transport";
|
||||
import { handleMcp } from "../mcp/handler";
|
||||
import { handleOpenApi } from "../openapi/handler";
|
||||
@@ -81,6 +82,11 @@ export function createApp(options: AppOptions = {}) {
|
||||
app.on(["GET", "HEAD"], "/sitemap.xml", (c) => handleSitemap({ head: c.req.method === "HEAD" }));
|
||||
app.on(["GET", "HEAD"], "/llms.txt", (c) => handleLlms({ head: c.req.method === "HEAD" }));
|
||||
|
||||
// Compresses only the web app's files and HTML shells: every route registered above answers before reaching
|
||||
// it, so API, MCP, and upload streams are never buffered or re-encoded. Where a CDN serves the static files
|
||||
// (Vercel), it also compresses at its edge.
|
||||
if (options.serveStatic !== false) app.use("/*", compress());
|
||||
|
||||
// Must precede the static middleware: serveStatic resolves "/" to dist/index.html and would
|
||||
// return it verbatim, skipping the OpenGraph/Twitter/canonical/JSON-LD injection in handleWebApp.
|
||||
app.on(["GET", "HEAD"], "/", (c) => handleWebApp(c.req.raw));
|
||||
|
||||
Reference in New Issue
Block a user