Skip to content

feat: status-page iframe - #2077

Merged
mxkaske merged 9 commits into
mainfrom
feat/status-page-iframe
Apr 16, 2026
Merged

feat: status-page iframe#2077
mxkaske merged 9 commits into
mainfrom
feat/status-page-iframe

Conversation

@mxkaske

@mxkaske mxkaske commented Apr 14, 2026

Copy link
Copy Markdown
Member

Summary

Adds ?iframe=<sections> query param to embed status pages chromelessly in third-party sites.

  • Section toggling: ?iframe=title,banner,components,feed — comma-separated, case-insensitive. Empty value shows all sections.
  • Theme override: ?theme=light|dark honored only in iframe mode via forcedTheme (no FOUC, no localStorage leak).
  • Internal links: open in a new tab (target="_blank") to keep the embedding page's iframe intact.
  • SEO: noindex via generateMetadata when iframe param is present.
  • Attribution: non-whitelabel pages show a centered "powered by openstatus.dev" in the embed; whitelabel pages hide the footer entirely.
  • CSP: frame-ancestors * globally (per-page allowlist deferred to v2).

Architecture

  • IframeShell wrapper sets data-iframe + inverted data-hide-* flags on a root div.
  • Sections hidden via CSS group-data-[hide-*=true]/iframe:hidden — no JS conditional rendering.
  • useIframe() hook backed by a nuqs createParser in lib/iframe-params.ts.
  • Server-side cache via createSearchParamsCache for generateMetadata.
  • common/link.tsx extended with unstyled variant + embed-aware target="_blank" logic.
  • ThemeProvider reads ?iframe + ?theme via nuqs, passes forcedTheme to next-themes.
  • page.tsx split into server wrapper (generateMetadata) + client.tsx (body).

Docs

New guide: apps/docs/src/content/docs/guides/how-to-embed-status-page-iframe.mdx

🤖 Generated with Claude Code

@vercel

vercel Bot commented Apr 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openstatus-status-page Ready Ready Preview, Comment Apr 16, 2026 6:28am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
openstatus-dashboard Skipped Skipped Apr 16, 2026 6:28am
openstatus-web Skipped Skipped Apr 16, 2026 6:28am

Request Review

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 14, 2026

Copy link
Copy Markdown

Deploying openstatus-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: a0a3351
Status: ✅  Deploy successful!
Preview URL: https://6b8a904b.openstatus-1sy.pages.dev
Branch Preview URL: https://feat-status-page-iframe.openstatus-1sy.pages.dev

View logs

@vercel
vercel Bot temporarily deployed to Preview – openstatus-web April 14, 2026 20:43 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-dashboard April 14, 2026 20:43 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-dashboard April 14, 2026 20:45 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-web April 14, 2026 20:45 Inactive

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 18 files

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/status-page/src/lib/iframe-params.ts">

<violation number="1" location="apps/status-page/src/lib/iframe-params.ts:44">
P2: This equality check is incorrect for arrays with duplicates; `every/includes` can return true for different section lists, causing false equality.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/status-page/src/lib/iframe-params.ts Outdated
@openstatusHQ openstatusHQ deleted a comment from claude Bot Apr 15, 2026
@vercel
vercel Bot temporarily deployed to Preview – openstatus-dashboard April 16, 2026 05:56 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-web April 16, 2026 05:56 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-status-page April 16, 2026 05:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-dashboard April 16, 2026 05:58 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-web April 16, 2026 05:58 Inactive

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/docs/src/content/docs/guides/how-to-embed-status-page-iframe.mdx">

<violation number="1" location="apps/docs/src/content/docs/guides/how-to-embed-status-page-iframe.mdx:83">
P2: This new caveat contradicts earlier docs that say embed mode hides the footer, making iframe behavior unclear. Clarify that the full footer is removed but attribution may still appear on non-whitelabel pages.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/docs/src/content/docs/guides/how-to-embed-status-page-iframe.mdx Outdated
@vercel
vercel Bot temporarily deployed to Preview – openstatus-web April 16, 2026 06:04 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-dashboard April 16, 2026 06:04 Inactive
@openstatusHQ openstatusHQ deleted a comment from claude Bot Apr 16, 2026
@vercel
vercel Bot temporarily deployed to Preview – openstatus-dashboard April 16, 2026 06:26 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-web April 16, 2026 06:26 Inactive
@openstatusHQ openstatusHQ deleted a comment from claude Bot Apr 16, 2026
@mxkaske

mxkaske commented Apr 16, 2026

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Apr 16, 2026

Copy link
Copy Markdown

Claude finished @mxkaske's task in 2m 57s —— View job


PR Review: feat/status-page-iframe

  • Gather context and understand the request
  • Read changed files
  • Review core iframe logic (embed-params.ts, use-embed.ts, embed-shell.tsx)
  • Review page routing changes (page.tsx, client.tsx, search-params.ts)
  • Review component changes (link, footer, header, theme-provider)
  • Review docs
  • Post final review

Overall this is a well-thought-out implementation. The CSS-driven visibility approach (data attributes + Tailwind group selectors) is clean and avoids JS-conditional rendering, keeping SSR consistent. A few things to look at:

Architecture

EmbedShell as a client wrapper around server layouts

embed-shell.tsx is "use client" and wraps the entire layout tree in both (public)/layout.tsx and (auth)/layout.tsx. Next.js App Router handles this correctly (children passed as RSC payloads are still server-rendered), but it's worth noting the entire layout's interactive boundary is now tied to nuqs URL parsing. This is fine in practice since nuqs reads the URL synchronously during SSR.

Issues found

1. Link component calls useEmbed() on every instance — consider a context

link.tsx:32

Every <Link> on the page now calls useQueryState("embed", embedParser) via useEmbed(). On pages with many links (feed, trackers, event cards), this is a significant number of duplicate URL parse calls per render. nuqs batches these efficiently, but it's still overhead per component.

A lighter alternative: expose embed mode via React context in EmbedShell and read it from context in Link. This way the URL is parsed once per page and shared.

// embed-context.tsx
const EmbedContext = createContext<EmbedState>(defaultState);
export const useEmbedContext = () => useContext(EmbedContext);

// embed-shell.tsx — provide instead of re-read
<EmbedContext.Provider value={{ mode, sections }}>
  <div ...>{children}</div>
</EmbedContext.Provider>

This is a non-blocking optimization suggestion, not a blocker.

2. serialize returns "" for mode: false — mismatch with nuqs sentinel

embed-params.ts:41

serialize(state) {
  if (!state.mode) return "";   // ← returns "", not null

nuqs expects null to remove a param from the URL. Returning "" would produce ?embed= in the URL, which the parser treats as mode: true with all sections — the opposite of the intent. The comment acknowledges this is safe for now (no writer exists), but if setEmbed is ever added without fixing this, disabling embed mode would silently re-enable it.

Suggest either returning null for !state.mode now, or adding an explicit // TODO: return null when exiting embed mode comment so this doesn't slip through when a writer is added.

Fix this →

3. not-found.tsxThemeProvider now reads embed/theme state unnecessarily

not-found.tsx:22

The 404 page uses ThemeProvider, which now calls useEmbed() and useQueryState("theme"). On a 404, there's no legitimate ?embed context, so this is wasted work. The Suspense wrapper was added to accommodate this, but the 404 page is outside the embed layout tree — EmbedShell isn't a parent, so data-embed is never set. The forcedTheme logic is harmless here (will always resolve to undefined), but it is unnecessary coupling.

This is minor and doesn't affect correctness.

4. CSP frame-ancestors * is globally applied

next.config.ts:17

Setting frame-ancestors * on all routes (including /events/report/:id, /monitors, auth routes) opens every page to embedding, not just the root status page. The PR description acknowledges per-page allowlisting is deferred to v2.

Consider narrowing this to the public status page routes only (/(status-page)/...) as a minimal defense against clickjacking on less-expected pages. That said, since the status page is purely read-only, the practical risk is low.

Looks Good

  • eq function uses a Set for O(1) lookups — correctly fixes the previous reviewer's concern
  • componentsVisible guard in client.tsx:64-80 properly skips the uptime API call when components are hidden — good optimization
  • isAnchor detection in link.tsx:41 correctly excludes #section hrefs from new-tab behavior, so in-page anchor scrolling works within the iframe
  • ?theme gated on embed.mode — prevents users on the full page from bookmarking forced themes, good call
  • Attribution logic in footer.tsx:39 is clean: JS-rendered null for whitelabel, CSS-hidden controls for non-whitelabel
  • generateMetadata sets noindex when embed mode is active — prevents search engines from indexing embedded views
  • Docs are clear and accurate after the recent fixes. The caveats section now correctly describes footer behavior

@mxkaske
mxkaske merged commit d14187b into main Apr 16, 2026
14 checks passed
@mxkaske
mxkaske deleted the feat/status-page-iframe branch April 16, 2026 06:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant