Symptom
Loading https://<pod>/profile/card.jsonld#me in a browser sometimes renders the mashlib data-browser view (expected), and sometimes renders raw RDF text (Turtle or JSON-LD) depending on cache state. A hard refresh always produces the mashlib view; a soft refresh can produce the raw text.
Root cause
Not a mashlib/webpack race — it's a JSS response-header inconsistency.
Variant (by Accept) |
Vary |
Cache-Control |
text/html (mashlib HTML wrapper) |
Accept |
no-store |
text/turtle |
Accept, Origin |
(missing) |
application/ld+json |
Accept, Origin |
(missing) |
Two problems:
- Vary mismatch across variants — some browser caches (observed in Brave/Chromium) get confused when variants of the same URL carry different Vary sets, and can serve the wrong variant on a matching request.
- Data variants have no Cache-Control — mashlib does an XHR for Turtle to populate the view; that response gets cached aggressively. On a soft refresh, the browser can serve the cached Turtle entry against the top-level HTML navigation.
Hard refresh sends Cache-Control: no-cache which bypasses the browser cache entirely → always goes upstream → gets the HTML wrapper → mashlib loads → view renders.
Security angle (why the fix matters beyond UX)
Solid responses vary with auth (WAC). Without client-side revalidation, a cached response from one auth state can leak into another (logged-in → logged-out body, or vice versa). The fix below addresses this in addition to the rendering race.
Proposed fix
- Emit consistent
Vary across all variants. Vary: Accept, Origin on every response (safer, covers CORS).
- Emit
Cache-Control: private, no-cache, must-revalidate on data variants (Turtle/JSON-LD/N3). Combined with the ETag we already send, this lets clients keep the body but always revalidate via If-None-Match → cheap 304s, no stale bodies, no auth-state leakage.
- Keep
Cache-Control: no-store on the HTML wrapper (it's a dynamic bootstrap template; no caching benefit).
Reproduction
for accept in "text/html,*/*;q=0.8" "text/turtle" "application/ld+json"; do
echo "=== $accept ==="
curl -s -D - -o /dev/null "https://<pod>/profile/card.jsonld" -H "Accept: $accept" \\
| grep -Ei "vary|cache-control|content-type"
done
Shows the Vary/Cache-Control mismatch documented above.
Symptom
Loading
https://<pod>/profile/card.jsonld#mein a browser sometimes renders the mashlib data-browser view (expected), and sometimes renders raw RDF text (Turtle or JSON-LD) depending on cache state. A hard refresh always produces the mashlib view; a soft refresh can produce the raw text.Root cause
Not a mashlib/webpack race — it's a JSS response-header inconsistency.
Accept)VaryCache-Controltext/html(mashlib HTML wrapper)Acceptno-storetext/turtleAccept, Originapplication/ld+jsonAccept, OriginTwo problems:
Hard refresh sends
Cache-Control: no-cachewhich bypasses the browser cache entirely → always goes upstream → gets the HTML wrapper → mashlib loads → view renders.Security angle (why the fix matters beyond UX)
Solid responses vary with auth (WAC). Without client-side revalidation, a cached response from one auth state can leak into another (logged-in → logged-out body, or vice versa). The fix below addresses this in addition to the rendering race.
Proposed fix
Varyacross all variants.Vary: Accept, Originon every response (safer, covers CORS).Cache-Control: private, no-cache, must-revalidateon data variants (Turtle/JSON-LD/N3). Combined with the ETag we already send, this lets clients keep the body but always revalidate viaIf-None-Match→ cheap 304s, no stale bodies, no auth-state leakage.Cache-Control: no-storeon the HTML wrapper (it's a dynamic bootstrap template; no caching benefit).Reproduction
Shows the Vary/Cache-Control mismatch documented above.