Skip to content
Danube Labs

Live website previews in a portfolio: what breaks and how to handle it

Embedding a client's live site in your portfolio is a great demo, until X-Frame-Options or a Content-Security-Policy quietly blanks the frame. A practical guide.

By Alexandru Secureanu2 min read

Our own portfolio shows client websites live, inside the page, so a visitor can scroll and click through the real thing. It is a better demo than a screenshot. It is also a small minefield, because a website decides for itself whether it may be embedded.

Two headers decide everything

A server can send X-Frame-Options: DENY (or SAMEORIGIN), or the newer Content-Security-Policy: frame-ancestors 'none'. Either one makes the browser refuse to render the page inside an <iframe> on another origin. You get a blank box or a browser error message, and there is no JavaScript event that tells you it happened.

Many hosting platforms and frameworks add these headers by default, for good reason: they stop clickjacking. Our founder's own portfolio sends frame-ancestors 'none', which is why it appears as a screenshot in our work section instead of a live frame.

Check on the server, not in the browser

Because the browser will not tell you, the check has to happen server-side. Our admin console has a "check embedding" button that sends a request to the URL and inspects the response headers. If either header forbids framing, the project is flagged and the site falls back to a screenshot with an "open live" link. The result is stored, so the public page never guesses.

Scale the frame, keep it usable

A desktop site squeezed into a 400-pixel card looks broken. We render the frame at a desktop width (1280 px) and scale it down with a CSS transform, so the card shows the site as its designer intended. Pointer events stay enabled, so visitors can scroll and click in the small preview. An expand button opens the same site full-screen with desktop, tablet and phone widths.

Do not load twenty sites at once

Each live frame is a full page load. We only create the frame when the card scrolls into view, and we show the screenshot until the frame has loaded. On slow connections the visitor still sees something immediately.

Sandbox with intent

The sandbox attribute restricts what the embedded page can do. We allow scripts, forms and same-origin access so the site behaves normally, and block top-level navigation so a link inside the frame cannot hijack the portfolio page.

Summary

  • Check X-Frame-Options and frame-ancestors on the server.
  • Store the result; fall back to a screenshot.
  • Render at desktop width and scale with a transform.
  • Load frames lazily, one per card, when visible.
  • Sandbox, but keep scripts and forms.
Share

Related articles

  • 8 min read

    Keys and Secrets in Development: Accidents, Not Attacks

    Most leaked credentials were never hidden well: they were committed, pasted, logged or shipped to the browser. The practical rules that keep them out, and how to rotate one that got through.

    • Developer tools
    • Security
  • 8 min read

    Row-Level Security in Supabase: Test It Before You Ship

    In Supabase the database is reachable through an API, so row-level security is the access control. Here is what it does, the mistake that exposes a table, and how to test a policy as anon and as a signed-in user.

    • Databases
    • Security