Using Builder.io with Vercel’s Password-Protected Deployments

Builder.io’s visual editor sometimes fails to load sites hosted on Vercel preview deployments that are protected by a password. This post outlines why the issue happens and a middleware-based workaround that may help unblock your workflow.

Why This Happens

Vercel’s Deployment Protection adds a _vercel_jwt cookie once the password is entered. However, this cookie is set with the SameSite=Lax attribute, which prevents it from being sent in cross-origin iframe requests—such as those made by the Builder.io editor when embedding your site.

As a result, even if you enter the password manually in your browser, the Builder.io iframe is not authenticated and cannot load the site properly.

Temporary Workaround (Next.js Middleware)
A practical workaround is to add custom middleware that rewrites the _vercel_jwt cookie to use SameSite=None. This allows it to be included in cross-origin requests from Builder.io. A secondary tracking cookie ensures the rewrite is only applied once per session.

Note: This workaround requires you to first load your protected site in a browser tab and enter the password. Once that’s done, Builder.io can access the site in the iframe. The cookie typically persists for 90 days.

Example Middleware

import { type NextRequest, type NextResponse } from 'next/server'
import { COOKIES } from '@src/utils/constants.util'

/**
 * Middleware to rewrite the _vercel_jwt cookie with SameSite=None,
 * allowing iframe access from Builder.io editor.
 */
export async function deploymentProtectionMiddleware(request: NextRequest, response: NextResponse) {
  const cookies = request.cookies.getAll()
  const vercelCookie = cookies.find((cookie) => cookie.name === COOKIES.vercelJwt)
  const vercelCookieTracking = cookies.find((cookie) => cookie.name === COOKIES.vercelJwtTracking)
  const isTracked = vercelCookie?.value === vercelCookieTracking?.value

  if (vercelCookie && !isTracked) {
    const jwt = vercelCookie.value
    const options = {
      path: '/',
      httpOnly: true,
      secure: true,
      sameSite: 'none' as const,
      maxAge: 90 * 24 * 60 * 60, // 90 days
    }

    response.cookies.set(COOKIES.vercelJwt, jwt, options)
    response.cookies.set(COOKIES.vercelJwtTracking, jwt, options)
  }

  if (!vercelCookie && vercelCookieTracking) {
    response.cookies.delete(COOKIES.vercelJwtTracking)
  }

  return response
}

export const deploymentProtectionMiddlewareConfig = {
  name: 'deploymentProtectionMiddleware',
  middleware: deploymentProtectionMiddleware,
}

Make sure COOKIES.vercelJwt maps to _vercel_jwt in your constants.

Additional Resources

This is a workaround rather than a long-term fix. We’re actively discussing improvements to how Builder handles protected environments, and we’re also in contact with Vercel about potential solutions. In the meantime, we hope this approach helps unblock your team.

If you’ve tried this or have other insights to share, please post them here—we’re happy to collaborate on improving the developer experience.

Best regards,