CodeGym /Kurse /ChatGPT Apps /Erster Smoke‑Test: „Hello Widget“ und openExternal

Erster Smoke‑Test: „Hello Widget“ und openExternal

ChatGPT Apps
Level 2 , Lektion 4
Verfügbar

1. Was ist ein Smoke‑Test für die ChatGPT App

In der gewöhnlichen Web‑Entwicklungswelt ist ein Smoke‑Test eine minimale Prüfung: „Lebt das System überhaupt?“ Die Seite öffnet sich, Buttons funktionieren, nichts Kritisches bricht zusammen.

In der Welt der ChatGPT‑Apps ist der Smoke‑Test etwas interessanter, weil in der Kette gleich mehrere Glieder beteiligt sind:

  1. Ihr Widget‑Code (React/Next.js).
  2. Next.js‑Dev‑Server.
  3. Tunnel (ngrok/Cloudflare).
  4. ChatGPT, das ein iframe erstellt und Ihr Widget in den Chat lädt.

Ein guter Smoke‑Test für uns ist, wenn:

  • das Widget innerhalb von ChatGPT ohne Fehler gerendert wird;
  • die grundlegende Interaktivität funktioniert (z. B. Klick auf einen Button öffnet einen externen Link);
  • weder in der Browser‑Konsole noch in den Logs des Dev‑Servers eine rote Fehlerlawine auftaucht.

Wichtig: In diesem Schritt testen wir noch keine MCP‑Tools, führen keine Lasttests durch und zählen keine Token‑Kosten. Unsere Aufgabe ist bescheiden und sehr praktisch: nachzuweisen, dass die Kette „Code → Next.js → Tunnel → ChatGPT → Nutzer“ überhaupt geschlossen ist.

Es ist hilfreich, sich das gedanklich als folgende Tabelle vorzustellen:

Was wird geprüft Woran erkennt man, dass alles ok ist
Widget‑Rendering In ChatGPT ist unser UI sichtbar, nicht ein „kaputtes iframe“
Verbindung ChatGPT ↔ unser Server Keine Fehler wie „Kann die App nicht laden“
JS‑Ausführung in der Sandbox Handler onClick werden tatsächlich ausgeführt
Möglichkeit, einen externen Link zu öffnen Der Button öffnet einen neuen Tab/ein neues Fenster mit der angegebenen URL

2. Unsere Übungs‑App: simples „Hello GiftGenius“

In diesem Kurs bauen wir schrittweise die App GiftGenius – einen Assistenten für Geschenkvorschläge. An diesem Punkt wählt sie noch nichts aus, kann aber zumindest höflich grüßen und einen Link „mehr erfahren“ anzeigen.

Wir brauchen ein minimales, aber ehrliches Widget: ohne komplexe Logik, aber mit echtem React‑Code.

Die einfachste Variante einer Widget‑Komponente könnte so aussehen (Namen und Styles können Sie anpassen; wir nehmen die Basis aus dem Kursplan):


// app/widget/page.tsx
'use client';

export default function GiftGeniusWidget() {
  return (
    <main style={{ padding: 16, fontFamily: 'system-ui, sans-serif' }}>
      <h1 style={{ fontSize: 24, marginBottom: 8 }}>
        Hello from GiftGenius
      </h1>
      <p style={{ marginBottom: 16 }}>
        Das ist Ihre erste ChatGPT App. Später bringen wir ihr bei, Geschenke auszuwählen.
      </p>
    </main>
  );
}

Ein paar wichtige Punkte.

Erstens macht die Direktive 'use client'; am Anfang der Datei die Komponente zu einer Client‑Komponente. Ohne sie behandelt Next.js die Datei als Server‑Komponente, und Sie können window, Handler wie onClick und überhaupt keine Browser‑APIs verwenden.

Zweitens ist dies eine ganz normale React‑Komponente. Keine „Apps‑SDK‑Magie“ ist darin sichtbar – und das ist ehrlich. Die ganze Magie, dass sie innerhalb von ChatGPT erscheint, steckt in der Konfiguration des MCP‑Servers und des Tools, das den Link auf die Widget‑URL zurückgibt. Darum kümmern wir uns später; jetzt interessiert uns nur das UI.

3. Widget in das Template einbetten und starten

Im offiziellen Next.js‑Template für das Apps SDK existiert die Widget‑Seite meist bereits; entweder bearbeiten Sie sie oder erstellen Ihre eigene unter der benötigten Route (z. B. /widget).

Nehmen wir an, Sie haben app/widget/page.tsx und ersetzen dessen Inhalt durch den obigen Code. Dann sieht die Kette so aus:

  1. Sie speichern die Datei.
  2. Der Next.js‑Dev‑Server (bereits gestartet via npm run dev) startet die nötigen Module neu, HMR aktualisiert die Seite.
  3. Über den Tunnel liefert Ihre öffentliche HTTPS‑URL unter demselben Pfad /widget das aktualisierte UI aus.

Das lässt sich auf zwei Arten überprüfen.

Zuerst ganz altmodisch – im lokalen Browser. Öffnen Sie:

http://localhost:3000/widget

und sehen dasselbe Hello from GiftGenius. Ja, das ist noch nicht ChatGPT; Sie stellen nur sicher, dass das UI Ihrer Next.js‑App lebt.

Dann – über den Tunnel. Nehmen Sie die ausgegebene URL (etwa https://witty-cat.ngrok-free.app), ergänzen Sie /widget und öffnen Sie sie in einem normalen Browser:

https://witty-cat.ngrok-free.app/widget

Wenn alles passt, sollte die Seite gleich aussehen. Das heißt, die Kette „Next.js → Tunnel → Ihr Browser“ funktioniert, es fehlt nur noch, ChatGPT dazwischenzuschalten.

4. Widget innerhalb von ChatGPT prüfen

ChatGPT erstellt im Dev Mode im Wesentlichen drei Dinge: Es erzeugt ein iframe, setzt dessen src auf Ihre öffentliche URL und lässt dieses iframe innerhalb einer Chat‑Nachricht laufen.

Vereinfacht sieht das Ereignis so aus:

sequenceDiagram
    participant Dev as Sie (Dev)
    participant Next as Next.js Dev‑Server
    participant Tun as Tunnel (HTTPS)
    participant GPT as ChatGPT
    participant User as Nutzer

    Dev->>Next: npm run dev (http://localhost:3000)
    Dev->>Tun: Tunnel zum Port 3000 starten
    GPT->>Tun: GET https://.../widget
    Tun->>Next: Proxy auf http://localhost:3000/widget
    Next-->>Tun: HTML + JS des Widgets
    Tun-->>GPT: Antwort mit HTML/JS
    GPT->>User: iframe mit Widget rendern

Um das Ergebnis zu sehen, gehen Sie so vor:

  1. Öffnen Sie ChatGPT im Browser und wählen Sie das passende Modell (meist GPT‑5.1 oder das im Dev Mode standardmäßig gesetzte).
  2. Wählen Sie Ihre App explizit aus (über Apps/Developer) oder „rufen“ Sie sie mit einem Satz wie: „Starte die App GiftGenius“.
  3. ChatGPT ruft Ihre App auf, der MCP‑Server gibt eine Antwort zurück, die den Link zum UI enthält (jenen /widget), und in der Chat‑Nachricht erscheint Ihr Widget.

Wenn alles gut ist, sehen Sie die vertraute Überschrift „Hello from GiftGenius“ direkt innerhalb von ChatGPT. An diesem Punkt ist der Smoke‑Test fast bestanden: Das iframe rendert, die Kette „Next.js → Tunnel → ChatGPT“ lebt. Es bleibt, den letzten Punkt aus unserer Tabelle zu prüfen – dass das Widget einen externen Link vorhersehbar öffnen kann. Dafür brauchen wir openExternal.

Später, wenn Sie den Code ändern, sieht der normale Dev‑Zyklus so aus:

  1. JSX ändern.
  2. Speichern.
  3. Entweder die ChatGPT‑Registerkarte aktualisieren oder (manchmal) reicht es, das Widget „anzustupsen“ – z. B. eine neue Nachricht senden oder die App erneut starten (abhängig von Template und Caching).

Wenn Änderungen nicht sichtbar sind, denken Sie zuerst an drei Verdächtige: Der Dev‑Server läuft nicht, der Tunnel ist abgerissen oder ChatGPT ist mit einer alten URL verbunden. Im Abschnitt „Wo suchen, wenn etwas schiefgeht“ besprechen wir dieses Szenario ausführlicher.

5. Warum nicht einfach <a href> setzen und vergessen

Um den letzten Punkt unseres Smoke‑Tests umzusetzen – einen Button, der eine externe Seite öffnet –, müssen wir openExternal einsetzen. Logische Frage: „Wozu überhaupt dieses openExternal? Was spricht gegen einen normalen Link?“

Das Problem ist, dass Ihr Widget nicht „einfach im Browser“ lebt, sondern in einem iframe unter der Kontrolle von ChatGPT. Dieses iframe läuft in einer recht strikten Sandbox: Es können Content‑Security‑Policy‑Einschränkungen, sandbox‑Attribute, Eigenheiten bei target="_blank" und Pop‑up‑Blocker wirken. Dadurch kann sich <ahref="…"> oder window.open() in einem solchen iframe unvorhersehbar verhalten: von völliger Ignoranz bis zu Einblendungen, die Ihr Code nicht kontrolliert.

Außerdem möchte OpenAI aus UX‑Sicht steuern, wann und wie Sie externe Seiten öffnen. Deshalb stellt das Apps SDK die einheitliche Brücke window.openai bereit: Ihr Code greift nicht direkt ins Elternfenster ein, sondern delegiert die Aktion an die Host‑App über eine klar definierte API.

6. API window.openai.openExternal: Was ist das und wie funktioniert es?

In der Widget‑Sandbox steht das globale Objekt window.openai zur Verfügung. Es ist die zentrale „Brücke“ zwischen Ihrem UI und ChatGPT: darüber lassen sich Tools aufrufen, Follow‑up‑Nachrichten senden, der Darstellungsmodus ändern, der Widget‑Zustand steuern und natürlich externe Links öffnen.

Uns interessiert in dieser Vorlesung eine konkrete Methode:

window.openai.openExternal({ href: string }): void;

Wenn Sie window.openai.openExternal({ href: 'https://example.com' }) aufrufen, dann:

  1. prüft ChatGPT, dass die URL durch Richtlinien erlaubt ist;
  2. kann ChatGPT dem Nutzer eine Warnung anzeigen (z. B. dass dies eine externe Seite ist);
  3. öffnet ChatGPT den Link in einem neuen Tab/Fenster des Browsers des Nutzers.

Wichtig sind zwei Dinge.

Erstens ist das eine rein clientseitige Operation. Sie ruft keine MCP‑Tools auf, geht nicht an Ihren Backend und verbraucht keine OpenAI‑Tokens. Es ist lediglich ein Signal an die Host‑App: „Bitte öffne diese URL.“

Zweitens ist diese Vorgehensweise kompatibel mit der Sandbox. ChatGPT entscheidet selbst, wie der Link geöffnet wird, ohne Ihrem iframe zu erlauben, mit window.open() über die Stränge zu schlagen.

7. Button mit openExternal in unser Widget einbauen

Lernen wir nun, einen externen Link aus unserem „Hello GiftGenius“ zu öffnen. Das einfachste Szenario: ein Button „Demo‑Link öffnen“, der beispielsweise zur Doku oder zur Landing‑Page Ihres Dienstes führt.

Schreiben wir zunächst einen kleinen Helper, damit TypeScript nicht meckert und das Widget nicht crasht, falls Sie /widget direkt im Browser öffnen (wo window.openai noch nicht vorhanden ist):

// app/widget/openExternalSafe.ts
export function openExternalSafe(href: string) {
  if (typeof window !== 'undefined' && (window as any).openai?.openExternal) {
    (window as any).openai.openExternal({ href });
  } else {
    // Fallback für lokale Ansicht ohne ChatGPT
    window.open(href, '_blank', 'noopener,noreferrer');
  }
}

Hier verwende ich absichtlich (window as any), um Sie nicht mit einer Typisierung von window.openai zu belasten. Später im Kurs beschreiben wir das Interface dieses Objekts sauber. Vorerst reicht es, dass der Code kompiliert und funktioniert.

Jetzt binden wir den Helper in unser Widget ein und fügen einen Button hinzu:

// app/widget/page.tsx
'use client';

import { openExternalSafe } from './openExternalSafe';

export default function GiftGeniusWidget() {
  return (
    <main style={{ padding: 16, fontFamily: 'system-ui, sans-serif' }}>
      <h1 style={{ fontSize: 24, marginBottom: 8 }}>
        Hello from GiftGenius
      </h1>
      <p style={{ marginBottom: 16 }}>
        Das ist Ihre erste ChatGPT App. Später bringen wir ihr bei, Geschenke auszuwählen.
      </p>
      <button
        type="button"
        onClick={() => openExternalSafe('https://example.com')}
        style={{
          padding: '8px 16px',
          borderRadius: 8,
          border: '1px solid #ccc',
          cursor: 'pointer',
        }}
      >
        Demo‑Link öffnen
      </button>
    </main>
  );
}

Was beim Klick passiert.

Wenn das Widget innerhalb von ChatGPT läuft, existiert window.openai.openExternal, und ChatGPT öffnet https://example.com so, wie es die Richtlinien vorsehen.

Wenn Sie http://localhost:3000/widget in einem normalen Browser geöffnet haben, existiert window.openai nicht, und der Fallback greift: Es öffnet sich ein neuer Tab mit den üblichen Browser‑Mitteln. Hier wird window.open nur beim direkten Öffnen von /widget im normalen Browser verwendet, also nicht innerhalb der ChatGPT‑Sandbox. In diesem Kontext funktioniert es wie gewohnt und bereitet keine Probleme.

Ausführlicher besprechen wir openExternal in Modul 3 (eigene Vorlesung zu Widget und Sandbox). Jetzt können Sie beruhigt zur Ausführung der App übergehen.

8. Mini‑End‑to‑End‑Smoke‑Test

Jetzt können wir einen vollständigen „Praxislauf“ machen. Gehen Sie die Schritte durch:

  1. Stellen Sie sicher, dass der Dev‑Server läuft (npm run dev) und Sie Hello from GiftGenius unter http://localhost:3000/widget sehen.
  2. Stellen Sie sicher, dass der Tunnel zum Port 3000 aktiv ist und die öffentliche URL sich in einem externen Browser öffnen lässt.
  3. Öffnen Sie ChatGPT, aktivieren Sie den Dev Mode und stellen Sie sicher, dass Ihre App mit der richtigen URL verbunden ist (öffentlich, nicht localhost).
  4. Öffnen Sie einen Chat, wählen Sie die App aus (oder bitten Sie das Modell, sie zu starten).
  5. Vergewissern Sie sich, dass im eingebetteten Widget „Hello from GiftGenius“ zu sehen ist.
  6. Klicken Sie auf den Button „Demo‑Link öffnen“ und prüfen Sie, dass im Browser https://example.com (oder Ihre Adresse) geöffnet wird.

Wenn all das funktioniert hat, bedeutet das:

  • HTML/JS des Widgets wird korrekt gebaut und vom Next‑Server ausgeliefert.
  • Der HTTPS‑Tunnel proxy’t die Anfragen korrekt.
  • ChatGPT vertraut Ihrer URL und kann das Widget laden.
  • window.openai funktioniert und übermittelt den Befehl zum Öffnen des externen Links.

Genau das wollten wir mit dem ersten Smoke‑Test erreichen.

9. Wo suchen, wenn etwas schiefgeht

Anders als beim „gewöhnlichen“ Frontend haben Sie hier drei Hauptorte für die Diagnose. Wichtig ist, schnell zu verstehen, in welchem davon es kaputt ist:

  1. Schauen Sie zuerst auf das UI in ChatGPT. Wenn Sie statt des Widgets eine Fehlermeldung wie „Error loading app“ oder „We had trouble talking to your app“ sehen, liegt das Problem höchstwahrscheinlich am Tunnel oder an der Erreichbarkeit Ihres Dev‑Servers. Versuchen Sie, die öffentliche URL direkt im Browser zu öffnen: Wenn sie nicht lädt oder mit einem Next.js‑Fehler lädt, beheben Sie das zuerst.
  2. Öffnen Sie dann die DevTools des Browsers in dem Tab, in dem ChatGPT läuft. Es gibt ein eigenes iframe für Ihr Widget und darin die vertraute Console‑Registerkarte. Wenn beim Klick auf den Button mit openExternal nichts passiert, prüfen Sie, ob Fehler wie „window.openai is undefined“ oder andere JS‑Fehler auftreten. Wenn ja, testen Sie das Widget vermutlich nicht in ChatGPT (sondern direkt über die Tunnel‑URL) oder haben die Direktive 'use client'; vergessen.
  3. Parallel dazu: Werfen Sie einen Blick ins Terminal mit npm run dev. Wenn dort Build‑Fehler (TypeScript, ESLint, Kompilation) auftauchen, sieht ChatGPT im besten Fall die alte Code‑Version, im schlimmsten Fall gar nichts. Wenn keine Fehler zu sehen sind, Sie aber keine Aktualisierungen sehen, vergewissern Sie sich, dass der Tunnel noch aktiv ist: Viele Tunnel‑Dienste schließen Sitzungen nach Inaktivitäts‑Timeout.

Ein weiterer typischer Fall: Alles funktioniert auf localhost, aber über den Tunnel erhalten Sie 404 oder eine seltsame Seite. Prüfen Sie dann sorgfältig den Basispfad (/widget vs /), die Einstellungen basePath/assetPrefix (falls bereits geändert) und die im Dev Mode eingetragene Adresse.

10. Kurz zur „Aufräumarbeit“: Prozesse beenden

Kleinigkeit, aber in der Praxis sehr nützlich. Anfänger vergessen oft, dass sowohl Dev‑Server als auch Tunnel eigene Prozesse sind, die im Hintergrund weiterlaufen.

Wenn plötzlich „Port 3000 ist bereits belegt“, versteckt sich möglicherweise irgendwo in den Tiefen der Terminals ein alter npm run dev. Unter Windows wird das manchmal zu lästigem Herumgefrickel im Task‑Manager; auf macOS und Linux hilft Ctrl + C in dem Terminal, in dem der Prozess läuft.

Gleiches gilt für den Tunnel: Wenn Sie mit mehreren Tunneln experimentiert oder einen alten nicht geschlossen haben, ist es leicht, den Überblick zu verlieren, an welche URL Ihre App im Dev Mode gerade gebunden ist. Gewöhnen Sie sich an: Wenn Sie die Session beenden, trennen Sie den Tunnel, stoppen Sie den Dev‑Server, und starten Sie beim nächsten Mal frisch.

11. Typische Fehler beim ersten Smoke‑Test

Fehler Nr. 1: localhost statt öffentlicher HTTPS‑URL verwenden.
Häufiger Fall: Im Dev Mode tragen Sie versehentlich http://localhost:3000 ein oder vergessen den Tunnel. Auf Ihrer Maschine funktioniert alles, aber ChatGPT, das in der Cloud läuft, kann localhost physisch nicht erreichen. Die Lösung ist einfach: Prüfen Sie, dass in den App‑Einstellungen wirklich die öffentliche HTTPS‑Adresse des Tunnels steht – mit dem richtigen Pfad (/mcp oder Root, je nach Template).

Fehler Nr. 2: die Direktive 'use client'; in der Widget‑Datei vergessen.
Sie schreiben schönen React‑Code, fügen onClick hinzu, greifen auf window.openai zu, und Next.js macht die Seite stillschweigend zur Server‑Komponente. Im besten Fall erhalten Sie „window is not defined“, im schlimmsten Fall baut sich die Komponente gar nicht. Um Zugriff auf Browser‑APIs zu haben, muss das Widget eine Client‑Komponente sein – darauf weist die erste Zeile 'use client'; hin.

Fehler Nr. 3: direkter Aufruf von window.open() statt openExternal.
Manchmal scheint es einfacher, window.open('https://example.com') zu verwenden. Im normalen Browser mag das funktionieren, aber innerhalb der ChatGPT‑Sandbox erhalten Sie unvorhersehbares Verhalten: von völligem Ignorieren bis zur Blockierung. Der richtige Weg für ChatGPT‑Apps ist window.openai.openExternal({ href }), der das Öffnen des Links dem Host delegiert und alle Sicherheitsrichtlinien einhält.

Fehler Nr. 4: TypeScript meckert über window.openai, und der Entwickler „heilt“ das durch das Abschalten der Typen.
In der Verzweiflung schreiben manche // @ts-nocheck an den Anfang der Datei. Das beseitigt Compiler‑Fehler, schaltet aber gleichzeitig TypeScript für die ganze Datei aus. Viel sicherer ist es, entweder gezielt as any um window zu verwenden oder in einer separaten Datei ein minimales Interface für window.openai zu beschreiben. In diesem Modul nutzen wir den kleinen Helper openExternalSafe mit (window as any); eine saubere Typisierung fügen wir später hinzu.

Fehler Nr. 5: Ergebnis nur auf localhost ansehen, aber nicht innerhalb von ChatGPT.
Es ist verlockend, sich damit zu begnügen, dass http://localhost:3000/widget lädt, und die Aufgabe als erledigt zu betrachten. Der Sinn dieses Moduls ist aber gerade, die App innerhalb von ChatGPT zu sehen. Dass im normalen Browser alles gut ist, garantiert noch nicht, dass ChatGPT das iframe korrekt erstellt, Ressourcen über den Tunnel lädt und nicht an CORS/CSP scheitert. Ein vollständiger Smoke‑Test enthält immer den Schritt mit dem echten Start der App in der ChatGPT‑Oberfläche.

Fehler Nr. 6: vergessener oder abgerissener Tunnel.
Sie haben den Code aktualisiert, aber in ChatGPT hängt die alte Widget‑Version oder es lädt gar nichts. Oft stellt sich heraus, dass der Tunnel wegen Timeout geschlossen wurde, der Developer Mode jedoch weiterhin auf die alte URL zeigt. Wenn beim Öffnen der Tunnel‑URL im normalen Browser ein Fehler erscheint – stellen Sie zuerst den Tunnel wieder her, bevor Sie dem Apps SDK die Schuld geben.

Fehler Nr. 7: die Konsole im iframe ignorieren.
Erfahrene SPA‑Entwickler sind es gewohnt, console.log in den DevTools ihrer App zu betrachten, aber innerhalb von ChatGPT ist es ein iframe, und man muss in den DevTools den richtigen Frame auswählen. Wenn Sie nur die oberste Ebene betrachten, sehen Sie womöglich keinen einzigen Fehler, obwohl im Widget längst alles rot ist. Die Gewohnheit, die DevTools „genau auf dem iframe‑Widget“ zu öffnen, spart viele Nerven.

1
Umfrage/Quiz
Die erste ChatGPT-Anwendung, Level 2, Lektion 4
Nicht verfügbar
Die erste ChatGPT-Anwendung
Die erste ChatGPT-Anwendung: Template, Dev Mode, Tunnel
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION