Skip to content

Vary: Accept dropped on HTML when the Link header is disabled (spec section 3) #72

Description

@abhay-codes07

Summary

Several adapters only set Vary: Accept on the HTML response as part of the Link-header injection. When the Link header is turned off (enableLinkHeader: false, or injectLinkHeader: false for nextjs/sveltekit), the negotiable HTML response goes out with no Vary header at all.

Spec

content-negotiation.md section 3:

A conformant server MUST set Vary: Accept on every response whose representation depends on the Accept header (i.e. every page that has both an HTML and markdown form).

That requirement is independent of whether the markdown twin is advertised via a Link header. Disabling the Link header does not stop the page from being content-negotiated, so Vary: Accept is still required.

Impact

A shared/CDN cache keyed on the URL alone can store the HTML response and later serve it to a client that sent Accept: text/markdown (or a bot), and vice versa. This is a cache-correctness bug, not just a missing header.

Where

The Vary append is nested inside the Link-header block (or gated by the same flag):

  • packages/cloudflare/src/worker.ts (block gated by enableLinkHeader &&)
  • packages/netlify/src/worker.ts (same)
  • packages/vercel/src/middleware.ts (same)
  • packages/nextjs/src/middleware.ts (appendVaryAccept is inside if (injectLinkHeader))
  • packages/sveltekit/src/handle.ts (if (!injectLinkHeader) return response before any Vary handling)

packages/deno/src/handler.ts and packages/fastly/src/handler.ts already do this correctly: they always set Vary: Accept and gate only the Link line.

Reproduction

import { createAEOWorker } from "@dualmark/cloudflare";
const worker = createAEOWorker({ upstream, enableLinkHeader: false });
// GET /page  (browser UA, Accept: text/html), upstream returns text/html
// actual:   Vary header is null
// expected: Vary: Accept

Proposed fix

Always run the Vary: Accept append on the negotiable HTML branch; gate only the Link header line on the flag, matching deno/fastly. PR incoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions