Query Parameters

By default no query parameter is part of the cache key, and none reaches the handler: /swatch, ?color=red, and ?color=red&utm=x all share one entry. Keying on the full query is what shatters a cache into countless entries — tracking tags and cache busters alone can destroy the hit rate — so query parameters are opt-in.

allowQuery is that opt-in, as an allowlist. Only the listed parameters vary the key and reach the handler; everything else is ignored:

const handler = defineCachedHandler(
  async (event) => {
    const url = event.url ?? new URL(event.req.url);
    const color = url.searchParams.get("color") ?? "default";
    return new Response(renderSwatch(color), {
      headers: { "content-type": "text/html" },
    });
  },
  {
    maxAge: 300,
    allowQuery: ["color"],
  },
);

// All of these share ONE cached entry (only `color` matters):
await handler({ req: new Request("https://shop.dev/swatch?color=red") });
await handler({ req: new Request("https://shop.dev/swatch?color=red&lang=en") });
await handler({ req: new Request("https://shop.dev/swatch?color=red&utm=ig") });

Matching is normalized and order-independent: ?color=red&size=l and ?size=l&color=red share an entry, and repeated array parameters such as ?color=red&color=blue match in any order.

Tip

Ignored parameters never reach the handler. Parameters outside the allowlist are stripped from the URL the handler receives — just like undeclared cookies and undeclared headers. A handler therefore cannot accidentally render output from a parameter the key does not cover.

Important

A handler that reads url.searchParams without allowQuery sees an empty query. Declare every parameter the output depends on.

allowQuery adds nothing to the response Vary header: query parameters live in the URL, and downstream caches already key on the URL.

#Keying the full query

allowQuery: true puts the whole query string in the key and passes it to the handler unchanged:

defineCachedHandler(myHandler, {
  maxAge: 300,
  allowQuery: true,
});

Use it for a route whose parameters are not known ahead of time, such as a proxy or a search endpoint. Every distinct query becomes its own entry, including tracking parameters, so prefer an allowlist where the parameter names are known.

#Edge cases

  • allowQuery: [] and allowQuery: false are the default. Both remove the whole query string, as an unset option does. Every request to the path shares one entry, and the handler always receives a query-less URL.

  • Names are case-sensitive. allowQuery: ["color"] does not match ?Color=red.

  • getKey overrides the key, not the filtering. A custom getKey decides the entire cache key, and allowQuery no longer affects it — but disallowed parameters are still stripped from the URL the handler receives:

    defineCachedHandler(myHandler, {
      maxAge: 300,
      allowQuery: ["color"], // still strips other params from the handler's URL
      getKey: (event) => new URL(event.req.url).pathname, // but this defines the key
    });