Creator guide

Make your site work in Vibras

Vibras plays every site in a frame, on its own domain. Browsers treat a framed site as a third party, which changes how its headers, cookies and sign-in behave. Find the symptom below, then send the creator the link to that section.

What you see, and where the fix is

Loading

It opens in a new tab instead of playing

The site sends X-Frame-Options or a Content-Security-Policy frame-ancestors rule that doesn't name Vibras, so browsers refuse to show it in a frame. Vibras shows a card that opens it in its own tab instead. My submissions shows the exact header a site sends. The fix is to allow Vibras in frame-ancestors. Browsers then ignore X-Frame-Options.

HTTP header
Content-Security-Policy: frame-ancestors 'self' https://www.vibras.si https://vibras.si
vercel.json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "frame-ancestors 'self' https://www.vibras.si https://vibras.si"
        }
      ]
    }
  ]
}
Netlify / Cloudflare _headers
/*
  Content-Security-Policy: frame-ancestors 'self' https://www.vibras.si https://vibras.si
Next.js (next.config)
async headers() {
  return [{ source: "/(.*)", headers: [{ key: "Content-Security-Policy", value: "frame-ancestors 'self' https://www.vibras.si https://vibras.si" }] }]
}
Express (Helmet)
app.use(helmet({
  contentSecurityPolicy: { directives: { frameAncestors: ["'self'", "https://www.vibras.si", "https://vibras.si"] } },
  frameguard: false,
}))
nginx
add_header Content-Security-Policy "frame-ancestors 'self' https://www.vibras.si https://vibras.si" always;
# and remove: add_header X-Frame-Options …;

If the site already sends a Content-Security-Policy, add the directive to that policy instead of sending a second one. Then press Test again in My submissions.

Sign-in

Signing in works, then it's signed out again

Most login libraries (Supabase, Auth.js/NextAuth, Firebase with cookies, express-session) store the session in a cookie with SameSite=Lax. Inside another site's frame, browsers silently refuse to save or send Lax cookies. The sign-in succeeds, the next page load has no cookie, and the user looks signed out.

The fix is to write the session cookies with SameSite=None; Secure; Partitioned when the page is framed. Partitioned gives the site a separate cookie jar that only exists inside Vibras, so its normal visits keep their usual cookies.

Detect that the page is framed
// True when the page runs inside another site, like Vibras.
const framed = window.top !== window.self && (() => {
  try { return window.top.location.origin !== location.origin } catch { return true }
})()
Supabase (@supabase/ssr)
import { createBrowserClient } from "@supabase/ssr"

const supabase = createBrowserClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  cookieOptions: framed
    ? { sameSite: "none", secure: true, partitioned: true }
    : undefined,
})
Any server: the cookie it sets
Set-Cookie: session=…; Path=/; Secure; HttpOnly; SameSite=None; Partitioned
Auth.js / NextAuth
// Auth.js / NextAuth: only if the site is meant to be played inside Vibras,
// since these options apply to every visit.
cookies: {
  sessionToken: {
    name: "__Secure-authjs.session-token",
    options: { httpOnly: true, sameSite: "none", secure: true, partitioned: true, path: "/" },
  },
}

Cookies set by the server also need these attributes. That includes token refreshes in middleware. A simple way to tell those requests apart is for the page to set a small partitioned marker cookie when it's framed, and have the server check for it. Safari blocks all third-party cookies, so there users may still need the site's own tab, or the storage access step under saves.

Sign-in

“Continue with Google” is blank or refuses to connect

Google, Apple, X, GitHub and most other sign-in providers forbid being shown in a frame. Inside Vibras, the site should open the provider in a popup. Vibras allows popups, and they open as normal windows. When the provider sends the user back, the popup hands the result to the framed page and closes. The framed page then finishes signing in, so the session lands in its cookies.

Supabase example
// Inside Vibras: run the provider in a popup, then finish here.
async function signInFramed(provider) {
  const popup = window.open("", "signin", "popup,width=500,height=700") // before any await
  const { data } = await supabase.auth.signInWithOAuth({
    provider,
    options: { redirectTo: location.origin + "/auth/popup", skipBrowserRedirect: true },
  })
  popup.location.href = data.url
  window.addEventListener("message", async (e) => {
    if (e.origin !== location.origin || e.source !== popup) return
    await supabase.auth.exchangeCodeForSession(e.data.code)
    location.reload()
  })
}

// /auth/popup (the page the provider returns to, in the popup):
window.opener.postMessage({ code: new URLSearchParams(location.search).get("code") }, location.origin)
window.close()

Only post the code to the site's own origin, as above. With PKCE, the code is only useful to the page that started the sign-in. Email and password sign-in needs no popup. It only needs the cookie fix above.

Storage

Saves from the site's own tab don't show up in Vibras

Browsers keep a framed site's cookies, localStorage and IndexedDB separate for each host site. Progress made on the site itself and progress made inside Vibras live in two different places. That's expected, and both keep working. To share them, store progress on the user's account on the server. Another option is to ask for storage access after a click.

Storage Access API
// Optional: share the site's own-tab storage, after a click (Safari asks first).
button.onclick = async () => {
  if (!(await document.hasStorageAccess())) await document.requestStorageAccess()
}

Loading

A bot check appears, or the frame stays blank

Cloudflare, Vercel and similar firewalls can challenge visitors. In a frame the challenge's cookie often can't be saved (Safari always blocks it), so the check repeats or never clears. Lower the security level for the game's page, or turn the challenge off for that path.

Loading

It loads, then goes blank or tries to leave Vibras

Some templates include a script that checks top.location and redirects out of frames. Vibras blocks that navigation, so the page can end up blank. Remove the script, or only run it when the page isn't framed by Vibras.

Phones

On phones it looks zoomed out

Without a mobile viewport tag, Vibras lays the page out at desktop width and scales it down to fit. Add this to the page's <head>:

<meta name="viewport" content="width=device-width, initial-scale=1">

Advanced

Threads or SharedArrayBuffer fail inside Vibras

Sites that send Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy to unlock SharedArrayBuffer aren't isolated inside a frame, so those features are off. Vibras opens these sites in their own tab. If the site can run without threads, offer a single-threaded fallback.

Fixed something? My submissions has a Test again button that checks the site right away.