惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Hackread – Cybersecurity News, Data Breaches, AI and More
Security Archives - TechRepublic
Security Archives - TechRepublic
I
Intezer
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
CXSECURITY Database RSS Feed - CXSecurity.com
A
Arctic Wolf
T
Threatpost
P
Proofpoint News Feed
AWS News Blog
AWS News Blog
C
Cybersecurity and Infrastructure Security Agency CISA
G
GRAHAM CLULEY
Cisco Talos Blog
Cisco Talos Blog
Simon Willison's Weblog
Simon Willison's Weblog
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
T
Tenable Blog
L
LINUX DO - 最新话题
Help Net Security
Help Net Security
WordPress大学
WordPress大学
Hacker News: Ask HN
Hacker News: Ask HN
人人都是产品经理
人人都是产品经理
MyScale Blog
MyScale Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Recent Announcements
Recent Announcements
Vercel News
Vercel News
The Hacker News
The Hacker News
J
Java Code Geeks
博客园 - 【当耐特】
D
Docker
V
V2EX
H
Heimdal Security Blog
GbyAI
GbyAI
博客园 - 叶小钗
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
News | PayPal Newsroom
The Register - Security
The Register - Security
The Cloudflare Blog
C
CERT Recently Published Vulnerability Notes
T
The Blog of Author Tim Ferriss
博客园 - Franky
MongoDB | Blog
MongoDB | Blog
SecWiki News
SecWiki News
S
Secure Thoughts
Attack and Defense Labs
Attack and Defense Labs
Microsoft Security Blog
Microsoft Security Blog
S
Schneier on Security
Latest news
Latest news
Project Zero
Project Zero

Vercel News

Vercel Open Source Program: Winter 2026 cohort How Notion Workers run untrusted code at scale with Vercel Sandbox How we run Vercel's CDN in front of Discourse From idea to secure checkout in minutes with Stripe Building Slack agents can be easy Scaling redirects to infinity on Vercel Advancing Python typing Gamma builds design-first agents with Vercel How Avalara turns pipe dreams into patent-pending with v0 Keeping community human while scaling with agents How OpenEvidence built a healthcare AI that physicians actually trust Security boundaries in agentic architectures Skills Night: 69,000+ ways agents are getting smarter Video Generation with AI Gateway We Ralph Wiggumed WebStreams to make them 10x faster How Stably ships AI testing agents in hours, not weeks How we built AEO tracking for coding agents Anyone can build agents, but it takes a platform to run them Introducing Geist Pixel The Vercel AI Accelerator is back with $6m in credits Making agent-friendly pages with content negotiation The Vercel OSS Bug Bounty program is now available Introducing the new v0 Run untrusted code with Vercel Sandbox, now generally available How Stripe built a game-changing app in a single flight with v0 How Sensay went from zero to product in six weeks AGENTS.md outperforms skills in our agent evals Agent skills explained: An FAQ Testing if "bash is all you need" AWS databases are now live on the Vercel Marketplace and v0 Use Perplexity Web Search with Vercel AI Gateway Introducing: React Best Practices Nick Bogaty joins Vercel as Chief Revenue Officer How Mux shipped durable video workflows with their @mux/ai SDK How to build agents with filesystems and bash How we made v0 an effective coding agent Stopping the slow death of internal tools Building AI-Generated Pixel Trading Cards with Vercel AI Gateway We removed 80% of our agent’s tools AI SDK 6 Our $1 million hacker challenge for React2Shell Cline now runs on Vercel AI Gateway How to prompt v0 Build smarter workflows with Notion and v0 Vercel launches partner certification Inside Workflow DevKit: How framework integrations work React2Shell Security Bulletin | Vercel Knowledge Base Billions of requests: Black Friday-Cyber Monday 2025 Investing in the Python ecosystem AWS Databases coming to the Vercel Marketplace How we built the v0 iOS app Workflow Builder: Build your own workflow automation platform Vercel Open Source Program: Fall 2025 cohort Self-driving infrastructure Vercel collaborates with Google for Gemini 3 Pro Preview launch Vercel: The anti-vendor-lock-in cloud How Nous Research used BotID to block automated abuse at scale How AI Gateway runs on Fluid compute What we learned building agents at Vercel Build and deploy data applications on Snowflake with v0 BotID Deep Analysis catches a sophisticated bot network in real-time Vercel achieves TISAX AL2 compliance to serve automotive partners Bun runtime on Vercel Functions David Totten Joins Vercel to Lead Global Field Engineering Vercel Ship AI 2025 recap You can just ship agents AI agents and services on the Vercel Marketplace Built-in durability: Introducing Workflow Development Kit Zero-config backends on Vercel AI Cloud Introducing Vercel Agent: Your new Vercel teammate Update regarding Vercel service disruption on October 20, 2025 Agents at work, a partnership with Salesforce and Slack Talha Tariq joins Vercel as CTO of Security Just another (Black) Friday Server rendering benchmarks: Fluid Compute and Cloudflare Workers Towards the AI Cloud: Our Series F Collaborating with Anthropic on Claude Sonnet 4.5 to power intelligent coding agents Preventing the stampede: Request collapsing in the Vercel CDN BotID uncovers hidden SEO poisoning How we made global routing faster with Bloom filters What you need to know about vibe coding Scale to one: How Fluid solves cold starts Addressing security & quality issues with MCP tools - Vercel AI agents at scale: Rox’s Vercel-powered revenue operating system Helly Hansen migrated to Vercel and drove 80% Black Friday growth Agentic Infrastructure Zero Data Retention on AI Gateway Optimizing Vercel Sandbox snapshots How Waldium made a blog platform work for humans and AI alike How FLORA shipped a creative agent on Vercel's AI stack Agent responsibly Making Turborepo 96% faster with agents, sandboxes, and humans Unified reporting for all AI Gateway usage new.website joins forces with v0 SERHANT.'s playbook for rapid AI iteration Two startups at global scale without DevOps Chat SDK brings agents to your users 360 billion tokens, 3 million customers, 6 engineers Meet the 2026 Vercel AI Accelerator Cohort Build knowledge agents without embeddings
Running Next.js in ChatGPT: How to Build ChatGPT Apps
Andrew QuChief of Software, Vercel · 2025-10-15 · via Vercel News

Running Next.js inside ChatGPT: A deep dive into native app integration

When OpenAI announced the Apps SDK with Model Context Protocol (MCP) support, it opened the door to embedding web applications directly into ChatGPT. But there's a significant difference between serving static HTML in an iframe and running a full Next.js application with client-side navigation, React Server Components, and dynamic routing.

This is the story of how we bridged that gap. We created a Next.js app that runs natively inside ChatGPT's triple-iframe architecture, complete with navigation and all the modern features you'd expect from a Next.js application.

Link to headingWhat ChatGPT apps and MCP enable

ChatGPT apps are interactive applications that run inside ChatGPT conversations. When you ask ChatGPT to "find me a hotel in Paris," a Booking.com widget appears directly in the chat with real search results you can interact with. These apps reach over 800 million ChatGPT users and appear naturally when relevant to the conversation.

ChatGPT apps are built on Model Context Protocol (MCP), an open standard that connects AI models to external tools and data. MCP works like REST or GraphQL, but for AI agents. Instead of hard-coding API calls, you define tools and resources in a structured format that models can discover and use. When ChatGPT needs to show your app, it fetches HTML from your MCP server and renders it in an iframe.

For Next.js developers, this means your existing application can run inside ChatGPT with the right patches, giving you immediate distribution without rebuilding from scratch. Our starter template gives you a working ChatGPT app you can deploy to Vercel right away.

Ready to build your own ChatGPT app?

Deploy our ChatGPT Apps SDK Next.js starter template to Vercel and start experimenting right away.

Deploy now

The template handles all the browser API patches and MCP server configuration automatically. ChatGPT's nested-iframe architecture breaks several Next.js features. Understanding how the patches fix these issues helps you customize and extend your app beyond the starter.

Link to headingHow ChatGPT's nested-iframe architecture breaks modern frameworks

ChatGPT renders apps in a three-layer iframe architecture to protect itself from potentially malicious apps. An iframe is an HTML element that embeds one webpage inside another, creating an isolated sandbox.

chatgpt.com

└── web-sandbox.oaiusercontent.com (sandbox iframe)

└── web-sandbox.oaiusercontent.com (inner iframe)

└── your app's HTML

The sandbox domain (web-sandbox.oaiusercontent.com) acts as a security buffer between ChatGPT and your app. This isolates apps from ChatGPT's main interface and protects users from potentially harmful code.

But this security model creates several technical challenges. When Next.js runs inside the deepest iframe, it thinks its origin is web-sandbox.oaiusercontent.com instead of your actual domain.

This behavior breaks several core features:

  1. Asset loading breaks: Next.js requests /_next/static/chunks/app.js from the sandbox domain instead of your actual domain, returning 404s

  2. Relative URLs resolve incorrectly: Images, fonts, and API calls using relative paths (like /logo.png or /api/data) resolve against the sandbox domain instead of your app's domain

  3. Browser history stores full URLs: history.pushState saves https://your-app.vercel.app/about, which exposes your real domain and breaks the sandbox security model

  4. Client-side navigation fails: When you click a Next.js Link, fetch requests go to the sandbox domain instead of your server

  5. CORS blocks React Server Components: Cross-origin requests from the iframe to your server fail without proper headers. Browsers also send OPTIONS preflight requests that Next.js doesn't handle by default

  6. Parent frames modify your HTML: ChatGPT occasionally adds attributes to your root <html> element, causing React hydration mismatches

  7. External links navigate inside the iframe: Links to external sites try to load inside the constrained iframe instead of opening in the user's browser

Let's explore how we solved each of these problems.

Link to headingMaking static assets load from your actual domain

Next.js generates asset paths like /_next/static/chunks/app-pages-browser.js. When running in the ChatGPT iframe, these requests resolve to https://web-sandbox.oaiusercontent.com/_next/static/chunks/app-pages-browser.js. But that URL doesn't exist. The assets live on your actual domain.

Next.js provides assetPrefix for exactly this scenario. Setting assetPrefix to your app's URL forces all /_next/ requests to use the correct origin:

import type { NextConfig } from "next";

import { baseURL } from "./baseUrl";

const nextConfig: NextConfig = {

assetPrefix: baseURL, // Forces /_next/ requests to use your-app.vercel.app

};

export default nextConfig;

The baseURL is dynamically determined based on the environment:

export const baseURL =

process.env.NODE_ENV == "development"

? "<http://localhost:3000>"

: "https://" +

(process.env.VERCEL_ENV === "production"

? process.env.VERCEL_PROJECT_PRODUCTION_URL

: process.env.VERCEL_BRANCH_URL || process.env.VERCEL_URL);

This ensures assets load correctly in development, preview, and production environments.

Link to headingSetting a base URL for all relative paths

Even with assetPrefix configured, other relative URLs still break. Images, fonts, and API calls using relative paths (like /logo.png, /fonts/inter.woff2, or /api/data) all resolve to https://web-sandbox.oaiusercontent.com instead of your domain. Next.js doesn't have a built-in way to rewrite every relative URL in your app.

The HTML <base> element solves this. It sets the base URL for all relative URLs in a document:

function NextChatSDKBootstrap({ baseUrl }: { baseUrl: string }) {

return (

<>

<base href={baseUrl}></base>

{/* Other bootstrap code... */}

</>

);

}

With this single element, every relative URL automatically resolves against your actual domain. This includes images (<img src="/logo.png">), stylesheets (<link href="/styles.css">), and API calls (fetch('/api/data')).

Link to headingPatching browser history to prevent URL leaks

Next.js uses the History API (history.pushState and history.replaceState) to update the URL bar during client-side navigation. However, when running inside an iframe with a different displayed origin, these APIs store full URLs like https://your-app.vercel.app/about. This exposes your real domain and breaks the sandbox security model.

Our solution is to intercept calls to history.pushState and history.replaceState, stripping them down to just the path, search params, and hash:

const originalReplaceState = history.replaceState;

history.replaceState = (state, unused, url) => {

const u = new URL(url ?? "", window.location.href);

const href = u.pathname + u.search + u.hash;

originalReplaceState.call(history, state, unused, href);

};

const originalPushState = history.pushState;

history.pushState = (state, unused, url) => {

const u = new URL(url ?? "", window.location.href);

const href = u.pathname + u.search + u.hash;

originalPushState.call(history, state, unused, href);

};

This ensures that navigation history entries only contain relative paths like /about instead of full URLs, preserving the iframe's security boundary while maintaining functional browser history and back/forward buttons.

Link to headingRewriting fetch requests for client-side navigation

Next.js uses a sophisticated client-side navigation system powered by React Server Components (RSC). When you click a Link, Next.js makes a fetch request to load the new page's RSC payload (a special format containing the component tree and data).

But remember, your app runs in an iframe at web-sandbox.oaiusercontent.com while the server is at your-app.vercel.app. Without intervention, these fetch requests fail because they target the wrong domain.

Our solution is to patch window.fetch to rewrite same-origin requests to use the correct base URL:

const appOrigin = new URL(baseUrl).origin;

const isInIframe = window.self !== window.top;

if (isInIframe && window.location.origin !== appOrigin) {

const originalFetch = window.fetch;

window.fetch = (input: URL | RequestInfo, init?: RequestInit) => {

// Parse the request URL from various input types

let url = /* ... parse input to URL ... */;

// If the request targets the iframe's origin, rewrite it

if (url.origin === window.location.origin) {

const newUrl = new URL(baseUrl);

newUrl.pathname = url.pathname;

newUrl.search = url.search;

newUrl.hash = url.hash;

return originalFetch.call(window, newUrl.toString(), {

...init,

mode: "cors", // Enable CORS for cross-origin RSC requests

});

}

return originalFetch.call(window, input, init);

};

}

This patch only runs when:

  1. The app is inside an iframe (window.self !== window.top)

  2. The iframe's origin differs from the app's actual origin

When these conditions are met, any fetch request to the iframe's origin is transparently rewritten to use the app's real origin, with CORS mode enabled for cross-origin requests.

Link to headingAdding CORS headers for cross-origin requests

With fetch patching in place, client-side navigation now makes cross-origin requests from the iframe to your server. But these requests will fail without proper CORS headers.

Modern browsers also send OPTIONS preflight requests before cross-origin POST requests (which Next.js uses for RSC payloads). Without handling these OPTIONS requests, navigation hangs.

Next.js middleware runs on every request before it's processed, which makes it the perfect place to add CORS headers and solve this problem:

import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {

// Handle OPTIONS preflight requests

if (request.method === "OPTIONS") {

const response = new NextResponse(null, { status: 204 });

response.headers.set("Access-Control-Allow-Origin", "*");

response.headers.set(

"Access-Control-Allow-Methods",

"GET,POST,PUT,DELETE,OPTIONS"

);

response.headers.set("Access-Control-Allow-Headers", "*");

return response;

}

// Add CORS headers to all responses

return NextResponse.next({

headers: {

"Access-Control-Allow-Origin": "*",

"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",

"Access-Control-Allow-Headers": "*",

},

});

}

export const config = {

matcher: "/:path*", // Apply to all routes

};

This middleware:

  1. Responds to OPTIONS requests with a 204 status and appropriate CORS headers

  2. Adds CORS headers to all responses so cross-origin fetch requests succeed

Now when Next.js makes an RSC request during navigation, the browser's preflight check passes and the actual request receives the data it needs.

Link to headingPreventing parent frame interference with DOM mutations

ChatGPT's parent frames occasionally modify the root <html> element by adding attributes. When React tries to hydrate, the server-rendered HTML doesn't match the DOM, causing hydration mismatch errors.

Our solution is to use a MutationObserver to watch for attribute changes on the root element and immediately remove unauthorized modifications:

const htmlElement = document.documentElement;

const observer = new MutationObserver((mutations) => {

mutations.forEach((mutation) => {

if (

mutation.type === "attributes" &&

mutation.target === htmlElement

) {

const attrName = mutation.attributeName;

if (attrName && attrName !== "suppresshydrationwarning") {

htmlElement.removeAttribute(attrName);

}

}

});

});

observer.observe(htmlElement, {

attributes: true,

attributeOldValue: true,

});

We also add suppressHydrationWarning to the <html> tag to prevent React from logging warnings:

<html lang="en" suppressHydrationWarning>

While this isn't a perfect solution (the parent frame shouldn't modify our HTML), it prevents the console from being flooded with hydration warnings.

Link to headingOpening external links in the user's browser

Without intervention, links to external sites try to navigate inside the constrained iframe. This creates a poor user experience when users click on documentation, blog posts, or other external resources.

ChatGPT provides an openai.openExternal() API specifically to handle this case. Our solution is to intercept clicks on external links and use this API to open them in the user's browser:

window.addEventListener(

"click",

(e) => {

const a = (e?.target as HTMLElement)?.closest("a");

if (!a || !a.href) return;

const url = new URL(a.href, window.location.href);

if (

url.origin !== window.location.origin &&

url.origin !== appOrigin

) {

try {

if (window.openai) {

window.openai.openExternal({ href: a.href });

e.preventDefault();

}

} catch {

console.warn("openExternal failed, likely not in OpenAI client");

}

}

},

true // Use capture phase to intercept before Next.js Link components

);

This ensures external links open in the user's browser instead of navigating inside the constrained iframe.

Link to headingConnecting your Next.js app to ChatGPT with MCP

With all the browser API patches in place, the final step is implementing the MCP server that connects ChatGPT to your Next.js app. MCP servers expose two main primitives: resources (content that can be displayed) and tools (actions the model can invoke).

Link to headingHow MCP resources serve HTML to ChatGPT

Resources serve HTML content that ChatGPT renders in iframes. We fetch the homepage HTML and register it as a resource:

const html = await getAppsSdkCompatibleHtml(baseURL, "/");

server.registerResource(

"content-widget",

"ui://widget/content-template.html",

{

title: "Show Content",

description: "Displays the homepage content",

mimeType: "text/html+skybridge",

_meta: {

"openai/widgetDescription": "Displays the homepage content",

"openai/widgetPrefersBorder": true,

},

},

async (uri) => ({

contents: [

{

uri: uri.href,

mimeType: "text/html+skybridge",

text: `<html>${html}</html>`,

_meta: {

"openai/widgetDescription": "Displays the homepage content",

"openai/widgetPrefersBorder": true,

},

},

],

})

);

The mimeType of text/html+skybridge tells ChatGPT to render this as an interactive widget. The _meta object contains OpenAI-specific configuration like the widget description and whether to show a border.

Link to headingHow MCP tools trigger app displays

Tools are actions ChatGPT can invoke. By linking a tool to a resource via openai/outputTemplate, ChatGPT knows to render the widget after the tool is called:

server.registerTool(

"show_content",

{

title: "Show Content",

description: "Fetch and display the homepage content with the name of the user",

inputSchema: {

name: z.string().describe("The name of the user to display"),

},

_meta: {

"openai/outputTemplate": "ui://widget/content-template.html",

"openai/toolInvocation/invoking": "Loading content...",

"openai/toolInvocation/invoked": "Content loaded",

"openai/widgetAccessible": false,

"openai/resultCanProduceWidget": true,

},

},

async ({ name }) => {

return {

content: [

{

type: "text",

text: name,

},

],

structuredContent: {

name: name,

timestamp: new Date().toISOString(),

},

_meta: {

"openai/outputTemplate": "ui://widget/content-template.html",

"openai/toolInvocation/invoking": "Loading content...",

"openai/toolInvocation/invoked": "Content loaded",

"openai/widgetAccessible": false,

"openai/resultCanProduceWidget": true,

},

};

}

);

The _meta object contains OpenAI-specific configuration:

  • openai/outputTemplate: Links to the registered resource URI

  • openai/toolInvocation/invoking: Text shown while the tool is executing

  • openai/toolInvocation/invoked: Text shown after completion

  • openai/widgetAccessible: Whether the widget should be keyboard/screen reader accessible

  • openai/resultCanProduceWidget: Signals that this tool can render a widget

Link to headingHow your app receives data from ChatGPT

The app can receive data from tool invocations via the window.openai.toolOutput property. ChatGPT automatically populates this object with the structuredContent from the tool's response:

const [name, setName] = useState<string | null>(null);

useEffect(() => {

if (typeof window === "undefined") return;

if (!window.openai) {

(window as any).openai = {};

}

let currentValue = (window as any).openai.toolOutput;

Object.defineProperty((window as any).openai, "toolOutput", {

get() {

return currentValue;

},

set(newValue: any) {

currentValue = newValue;

if (newValue?.name) {

setName(newValue.name);

}

},

configurable: true,

enumerable: true,

});

if (currentValue?.name) {

setName(currentValue.name);

}

}, []);

This reactive pattern allows the app to update its UI whenever ChatGPT invokes a tool. The property setter intercepts changes to toolOutput and updates React state accordingly.

Link to headingUsing React hooks to manage ChatGPT integration

The manual window.openai approach shown above works, but it's verbose and requires boilerplate in every component that needs ChatGPT integration. For production applications, we built a collection of React hooks that abstract the browser API into type-safe patterns.

These hooks handle server-side rendering, optimize re-renders, and provide type safety. Three core hooks cover most use cases:

useSendMessage sends followup messages to ChatGPT:

const sendMessage = useSendMessage();

// Trigger a new ChatGPT message from user interaction

<button onClick={() => sendMessage("Show me more examples")}>

More Examples

</button>

useWidgetProps accesses tool call outputs with type safety:

const toolOutput = useWidgetProps<{ name?: string }>();

// Access structured data from the tool invocation

const name = toolOutput?.name;

useDisplayMode customizes your interface based on display mode:

const displayMode = useDisplayMode();

// Render different layouts based on how ChatGPT displays the app

return displayMode === "fullscreen" ? <FullView /> : <CompactView />;

The full collection of hooks is available in the ChatGPT Apps SDK Next.js Starter.

Link to headingThe advantages this approach unlocks

This approach provides several significant advantages over simpler iframe embedding.

Link to headingNative Next.js navigation

Users can click links and navigate between pages using Next.js's client-side routing. The browser's back and forward buttons work correctly. Navigation is fast thanks to React Server Components streaming page updates instead of reloading the entire iframe.

Without these patches, you'd be limited to serving a single static page or adding yet another iframe layer, which would introduce even more complexity.

Link to headingFull Next.js feature set

You're not limited to static HTML. All of Next.js's features work normally:

  • React Server Components and streaming

  • Server Actions for forms

  • Incremental Static Regeneration (ISR)

  • Dynamic routing with [slug] patterns

  • API routes

  • Middleware

Link to headingUnchanged developer experience

The patches are applied once in layout.tsx. The rest of your codebase uses familiar Next.js patterns without workarounds. You can develop locally with next dev and deploy to Vercel like any other Next.js app.

Link to headingPerformance that matches standard Next.js apps

Client-side navigation is faster than full page reloads because only the new page's data transfers over the network, not the entire document. React Server Components enable streaming, so users see content faster on slower connections.

Link to headingNative-feeling user experience

The app feels integrated into ChatGPT rather than bolted on. Users can:

  • Navigate naturally with working back/forward buttons

  • See instant page transitions

  • Open external links in their browser

  • Experience smooth interactions without iframe nesting issues

Link to headingGetting started with Next.js in ChatGPT

These seven patches solve the fundamental incompatibilities between ChatGPT's iframe architecture and Next.js's expectations. The starter template implements all of them, so you can focus on building your app's features instead of wrestling with browser APIs.

Whether you're building productivity tools, data visualizations, or interactive experiences, this foundation brings the full power of Next.js to ChatGPT's 800 million users.

Ready to build your own ChatGPT app?

Deploy our ChatGPT Apps SDK Next.js starter template to Vercel and start experimenting right away.

Deploy now