Quick Answer
Mixed content occurs when an HTTPS page loads resources - images, scripts, stylesheets, or API calls - over HTTP. Modern browsers block mixed content by default, which means those resources silently fail to load. In AI-built apps, this typically stems from hardcoded HTTP URLs in source code, third-party embeds, or assets referenced from a CMS or database with old URLs.
Why Mixed Content Is a Browser-Level Security Block
When your site runs on HTTPS, the browser establishes an encrypted connection that protects everything transmitted between your server and the user. Loading any resource over HTTP breaks that guarantee. An attacker on the same network could intercept and modify the HTTP resource - injecting malicious JavaScript into a script file, replacing an image, or altering a stylesheet - before it reaches the user, even though the page itself is served securely.
For this reason, browsers do not just warn about mixed content - they actively block it. Under the W3C Mixed Content specification, "active mixed content" (scripts, stylesheets, iframes, and fetch requests) is always blocked, while images, audio and video are "upgradeable": the browser requests them over HTTPS instead, and blocks them if that fails.
Chrome works this way today: it upgrades mixed images, audio and video to HTTPS, does not load them if the upgrade fails, and blocks every other type of mixed content outright. This means any site with mixed content is experiencing real functional failures - not just theoretical security warnings.
For vibe-coded apps deployed on Vercel or Netlify - which automatically provision HTTPS via Let's Encrypt - mixed content is an especially common issue because the AI generates code assuming HTTP in many contexts, and the HTTPS layer is added by the deployment platform rather than the developer.
Where Mixed Content Comes From in AI-Built Apps
| Source | Example | Type |
|---|---|---|
| Hardcoded API URLs | fetch('http://api.example.com/data') |
Active (blocked) |
| External script tags | <script src="http://cdn.example.com/lib.js"> |
Active (blocked) |
| Image src attributes | <img src="http://images.example.com/photo.jpg"> |
Passive (upgraded, blocked if no HTTPS) |
| CSS background images | background: url('http://cdn.example.com/bg.png') |
Passive (upgraded, blocked if no HTTPS) |
| Iframe embeds | <iframe src="http://embed.example.com/"> |
Active (blocked) |
| WebSocket connections | new WebSocket('ws://...') instead of wss:// |
Active (blocked) |
| Database-stored URLs | User-uploaded content with old HTTP URLs | Passive or Active |
What AI Code Generates That Causes Mixed Content
When Cursor or Bolt scaffolds a Next.js app and you ask it to "add a video embed" or "fetch data from an external API," it commonly generates HTTP URLs in several patterns:
// ❌ BAD - HTTP URLs that cause mixed content on an HTTPS site
const API_BASE = 'http://api.myservice.com';
// Hardcoded HTTP fetch
const res = await fetch('http://api.myservice.com/products');
// External script loaded over HTTP
// (placed by AI in _document.js or layout.tsx)
<script src="http://cdn.analytics.com/track.js"></script>
// WebSocket connection over ws:// instead of wss://
const socket = new WebSocket('ws://realtime.myservice.com:3001');
// Image from a CMS URL stored without protocol upgrade
<img src={`http://images.cms.io/${product.imageId}.jpg`} alt={product.name} />
These are not theoretical issues. They are patterns that appear in real Cursor, Bolt, and Lovable-generated codebases. The AI has no way to know that your deployment will be served over HTTPS; it generates the URL it has context for, which is often the development or staging endpoint using HTTP.
How to Fix Mixed Content Issues
The fix depends on the source of the mixed content, but the principle is always the same: upgrade every resource URL to use https:// or use a protocol-relative URL (//) where supported.
// ✅ GOOD - Protocol-safe API calls and resource URLs
// Use HTTPS explicitly in environment variables
const API_BASE = process.env.NEXT_PUBLIC_API_URL; // Set to https:// in Vercel env vars
// Protocol-relative URL (inherits https from the page)
<script src="//cdn.analytics.com/track.js"></script>
// WebSocket using wss:// (TLS-secured WebSocket)
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(`${wsProtocol}//realtime.myservice.com`);
// Upgrade image URLs before rendering
const secureImageUrl = imageUrl.replace(/^http:\/\//i, 'https://');
<img src={secureImageUrl} alt={product.name} />
For environment variables used in fetch calls, ensure that the values you set in Vercel's dashboard always use the https:// scheme. A common mistake is setting NEXT_PUBLIC_API_URL=http://... during development and forgetting to update it for production.
The Upgrade-Insecure-Requests Header
As a belt-and-suspenders defense, you can instruct browsers to automatically upgrade HTTP sub-resource requests to HTTPS before they are blocked. This is done via a Content Security Policy directive or an HTTP response header:
// ✅ GOOD - Add to next.config.js headers for automatic protocol upgrade
// This catches any remaining HTTP URLs and upgrades them to HTTPS
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: 'upgrade-insecure-requests'
}
];
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
The upgrade-insecure-requests directive tells browsers to replace http:// with https:// for all resource loads on the page. It is not a substitute for fixing the URLs at their source - a resource that does not exist over HTTPS will still fail - but it prevents the browser from blocking resources that do exist on both protocols.
How to Scan for Mixed Content on Your Live Site
There are three practical ways to identify mixed content on a deployed site:
Browser DevTools: Load each page of your app in Chrome, open DevTools (F12), and check the Console tab for "Mixed Content" messages and the Network tab filtered by "blocked" or HTTP requests from an HTTPS origin. This is thorough but manual and does not scale across a large site.
Automated headless browser scan: A Playwright or Puppeteer script can load every page of your site and capture all mixed content warnings programmatically. This can be integrated into your CI/CD pipeline on Vercel as a post-deploy check.
Tools like VibeDoctor's Vibe Check load your live page in a real browser, flag every HTTP resource on an HTTPS page by its URL, and report page weight and request count. Free to sign up.
CSP reporting: Deploying a Content-Security-Policy-Report-Only header that only allows https: sources, with a report-uri or report-to directive, sends a report to an endpoint you control whenever a page tries to load something over HTTP. Services like Report URI aggregate these reports across all real user sessions, giving you a comprehensive view of mixed content in the wild.
Page Weight and Request Count: Related Issues
While fixing mixed content, it is worth addressing a related WEB check that affects the same pages. The WEB-003 check covers page weight and HTTP request count - issues that frequently co-exist with mixed content in AI-built apps.
Vibe-coded apps often load dozens of external scripts (analytics, chat widgets, A/B testing tools, font providers) that were added across multiple AI prompting sessions without considering their cumulative impact. Each external HTTP request adds latency, and each script that loads over HTTP triggers a mixed content block. A single audit of your page's Network tab often reveals 10-20 external requests that can be consolidated, deferred, or removed entirely.
According to the HTTP Archive's 2024 Web Almanac, the median web page makes 71 HTTP requests on desktop and 66 on mobile. Every extra request is one more resource that can slow the page down, and one more chance of a mixed content warning if any of them is loaded over HTTP.
FAQ
My browser shows a padlock icon - does that mean I have no mixed content?
Not necessarily. Chrome no longer shows a padlock at all (since Chrome 117 it shows a neutral "tune" icon for HTTPS pages), and browsers now quietly upgrade or block mixed images, audio and video, so the address bar may not change. The icon only confirms the page's main document was served over HTTPS. Open DevTools and check the Console for "Mixed Content" messages to get the full picture.
What is the difference between active and passive mixed content?
Active mixed content includes resources that can modify the DOM or execute code: scripts, stylesheets, iframes, XMLHttpRequest, fetch, and WebSockets. These are always blocked. Passive mixed content includes images, audio, and video - resources that cannot execute code. Chrome upgrades these to HTTPS and blocks them if they are not available over HTTPS. From a security standpoint, active mixed content is the critical category to resolve first.
Does Vercel automatically serve my site over HTTPS?
Yes, Vercel serves every deployment over HTTPS, including preview deployments, with certificates it gets from Let's Encrypt automatically. However, this only secures the main document - your source code is still responsible for ensuring all sub-resources are loaded over HTTPS. Vercel does not upgrade HTTP resource URLs in your code.
My Supabase Storage images are loading over HTTP - how do I fix that?
Supabase Storage URLs always support HTTPS. If you are getting HTTP URLs from the Supabase client, check whether you are calling getPublicUrl() correctly - it returns a full URL including protocol. Ensure your Supabase project URL in NEXT_PUBLIC_SUPABASE_URL uses https://, as the Storage client derives the base URL from your project configuration.
Can upgrade-insecure-requests break anything on my site?
It can break resources that genuinely do not exist over HTTPS. If you embed content from a third-party service that only supports HTTP, upgrading the request to HTTPS will cause a connection failure. This is a signal that you should replace that resource with an HTTPS-capable alternative rather than keeping an insecure dependency. In practice, almost every CDN and public API has supported HTTPS for years, so breakage from this directive is rare on modern stacks.