Native Next.js Integration in ChatGPT: In-Depth Analysis - Vercel

# Running a Full Next.js App Natively Inside ChatGPT

When **OpenAI announced the Apps SDK with Model Context Protocol (MCP) support**, it enabled embedding rich web applications directly inside ChatGPT.  
But serving static HTML in an iframe is straightforward — running a full **Next.js app** with client-side navigation, React Server Components, and dynamic routing inside ChatGPT’s **triple-iframe sandbox** requires engineering work.

This guide documents how we bridged that gap, and how you can run a modern Next.js app seamlessly inside ChatGPT.

---

## What ChatGPT Apps & MCP Enable

[ChatGPT Apps](https://openai.com/index/introducing-apps-in-chatgpt/) are **interactive widgets** that run inside conversations.  
Example: *“Find me a hotel in Paris”* might surface a Booking.com widget with live search results — without leaving the chat.

**Key points:**
- Built on **[Model Context Protocol](https://vercel.com/blog/model-context-protocol-mcp-explained)** (MCP) — an open standard linking AI models to external tools and data.
- ChatGPT discovers and uses tools/resources dynamically, fetching your app’s HTML from an MCP server, then rendering it in its own iframe.

---

> **Tip:** See our [Next.js Starter Template](https://github.com/vercel-labs/chatgpt-apps-sdk-nextjs-starter) — deployable to [Vercel](https://vercel.com/templates/ai/chatgpt-app-with-next-js) — to get a ready-to-run example with all necessary patches.

---

## How ChatGPT’s Triple-Iframe Structure Causes Issues

**Architecture:**

chatgpt.com

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

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

└── your app's HTML


### Challenges for Next.js:
- Origin mismatch (app thinks it runs on `web-sandbox.oaiusercontent.com`)
- Asset loading failures (`/_next` chunks from wrong domain)
- Broken client-side navigation
- CORS errors with React Server Components
- HTML mutations causing hydration mismatch
- External links trapped inside iframe

---

## 7 Core Patches to Fix Compatibility

### 1. **Load Static Assets from Your Own Domain**
Use `assetPrefix` in `next.config.ts`:

import type { NextConfig } from "next";

import { baseURL } from "./baseUrl";

const nextConfig: NextConfig = {

assetPrefix: baseURL,

};

export default nextConfig;


And `baseUrl.ts`:

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);


Ensures `/​_next` assets resolve correctly in dev, preview, production.

---

### 2. **Set a Base URL for Relative Paths**


Applied in `layout.tsx` — fixes images, fonts, API calls that use relative paths.

---

### 3. **Patch Browser History to Prevent Origin Leaks**

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);

};


Also patch `pushState` similarly — keeps URLs relative, protecting sandbox boundaries.

---

### 4. **Rewrite Fetch Requests for Client-Side Navigation**
Override `window.fetch` to point same-origin requests back to your real domain:

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

const originalFetch = window.fetch;

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

// parse input...

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

const newUrl = new URL(baseUrl);

newUrl.pathname = url.pathname;

// ...

return originalFetch.call(window, newUrl.toString(), { ...init, mode: "cors" });

}

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

};

}


---

### 5. **Add CORS Headers via Middleware**

// middleware.ts

export function middleware(request: NextRequest) {

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

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

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

// ...

return res;

}

return NextResponse.next({

headers: {

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

// ...

},

});

}

export const config = { matcher: "/:path*" };


Handles **preflight OPTIONS** requests and adds universal CORS headers.

---

### 6. **Prevent Parent Frame HTML Mutations**
Use `MutationObserver` to strip unauthorized attributes from ``:

const html = document.documentElement;

new MutationObserver(mutations => {

mutations.forEach(m => {

if (m.type === "attributes" && m.attributeName !== "suppresshydrationwarning") {

html.removeAttribute(m.attributeName!);

}

});

}).observe(html, { attributes: true });


Add `suppressHydrationWarning` to ``.

---

### 7. **Open External Links in User’s Browser**
Intercept click events:

window.addEventListener("click", e => {

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

if (!a) return;

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

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

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

e.preventDefault();

}

}, true);


Uses `openai.openExternal()` to break out of iframe.

---

## Integrating MCP in Your Next.js App

MCP exposes:
- **Resources** — HTML or other content ChatGPT renders.
- **Tools** — Model-invoked actions.

### Register a Resource:

server.registerResource(

"content-widget",

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

{ title: "Show Content", mimeType: "text/html+skybridge" },

async uri => ({

contents: [{ uri: uri.href, mimeType: "text/html+skybridge", text: html }],

})

);


### Register a Tool:

server.registerTool(

"show_content",

{

title: "Show Content",

inputSchema: { name: z.string() },

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

},

async ({ name }) => ({

structuredContent: { name, timestamp: new Date().toISOString() }

})

);


---

## Receiving Tool Output in Your App
Watch `window.openai.toolOutput` and sync to state:

const [name, setName] = useState(null);

useEffect(() => {

if (window.openai?.toolOutput?.name) setName(window.openai.toolOutput.name);

}, []);


---

## Recommended React Hooks for Apps SDK
Encapsulate repetitive logic:

- `useSendMessage()` — programmatically send messages to ChatGPT
- `useWidgetProps()` — type-safe tool output access
- `useDisplayMode()` — adjust UI for widget/fullscreen display

---

## Advantages of This Approach
- **Full Next.js feature set**: RSC, streaming, server actions, ISR, dynamic routing, API routes
- **Native-feeling UX**: Back/forward navigation, instant transitions
- **Single patch location**: Applied once in `layout.tsx`
- **Performance**: Faster than full iframe reloads

---

## Get Started Quickly
Deploy the [Next.js Starter Template](https://github.com/vercel-labs/chatgpt-apps-sdk-nextjs-starter).  
All 7 patches are included — you can focus on building features, not iframe workarounds.

For broader AI-driven content publishing alongside your ChatGPT apps, consider **[AiToEarn](https://aitoearn.ai/)** — an open-source platform integrating AI content generation, multi-platform publishing, analytics, and monetization.

Read more

Google DeepMind Launches CodeMender: An Intelligent Agent for Automatic Code Repair

Google DeepMind Launches CodeMender: An Intelligent Agent for Automatic Code Repair

Google DeepMind Launches CodeMender — AI for Automated Software Vulnerability Repair Date: 2025-10-18 13:09 Beijing --- Introduction Google DeepMind has unveiled CodeMender, an AI-powered intelligent agent designed to automatically detect, fix, and strengthen software vulnerabilities. Built on cutting-edge reasoning models and program analysis technologies, CodeMender aims to dramatically cut the

By Honghao Wang
What Signal Is Behind People’s Daily’s Consecutive Interviews with Entrepreneurs?

What Signal Is Behind People’s Daily’s Consecutive Interviews with Entrepreneurs?

Anti-Overcompetition — Urgent Action Needed! --- Source: Reprinted from the WeChat public account 笔记侠PPE书院 (bijixiafuwu) (authorized). Contact the original publisher for permission before reprinting. Article stats: 9,929th in-depth piece | 5,625 words | ~16 min read --- Understanding Overcompetition (Involution) Editor’s note: Overcompetition (内卷) is now a serious concern in

By Honghao Wang