docs: direct-embed vs staging framing contract — serving-layer owns frame-ancestors (direct=* , staging=self); per-host recipes
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

This commit is contained in:
doxa-maps 2026-09-11 23:38:31 +00:00
parent ee3ebf8700
commit 9cc8053ec2

View file

@ -55,3 +55,28 @@ Both are supported on **built** pages. The one "never iframe" rule applies only
staging** script (`internal/staging.js`), which mounts elements directly because a dev-server
iframe crosses realms (`instanceof` breaks). Built pages have no such problem — the generated
staging (`staging-forge.js`) itself uses an iframe stage.
## Direct-embed vs staging — the framing contract (serving layer)
The built tree has TWO kinds of page, and they need DIFFERENT framing policy:
| Surface | Paths | `frame-ancestors` |
|---|---|---|
| **Direct embed** (the product) | `<bundle>/<profile>/index.html`, `<bundle>/<profile>/<instance>/index.html` — the bare map pages the manifest's `iframeUrl` points at | **`*`** — framable on any partner site (that's the whole point) |
| **Staging** (internal preview) | tree root `index.html`, per-bundle `<bundle>/index.html`, the SPA shell | **`'self'`** — preview only |
`frame-ancestors` can ONLY be set by an HTTP response header (HTML `<meta>` cannot set it), so
**the server/CDN owns this split — the bundler cannot enforce it.** Whoever serves the tree must
apply it. Rule of thumb: **a path with ≥2 directory segments under the tree root is a direct-embed
page → serve it framable.**
Portable ways to implement the split, by host:
- **Traefik** (nuxtbase/go-base): a higher-priority router on `PathRegexp(^/plugin-assets/[^/]+/[^/]+/)`
sets `Content-Security-Policy: frame-ancestors *` + clears `X-Frame-Options`. (Live example:
`/data/coolify/proxy/dynamic/doxa-maps-embed.yaml`.)
- **Cloudflare Pages / Netlify**: a `_headers` file — `/<bundle>/*/* Content-Security-Policy: frame-ancestors *`.
- **Nginx**: `location ~ ^/[^/]+/[^/]+/ { add_header Content-Security-Policy "frame-ancestors *"; }`
- **GitHub Pages**: serves no custom headers → pages are framable by default (no frame-ancestors), which is already correct for direct embeds.
If you serve the WHOLE tree with one blanket `frame-ancestors 'self'`, direct embeds break on
partner sites (the staging policy leaks onto the product pages). That is the one mistake to avoid.