CodeGym /Corsi /ChatGPT Apps /Primo smoke test: «Hello widget» e openExternal

Primo smoke test: «Hello widget» e openExternal

ChatGPT Apps
Livello 2 , Lezione 4
Disponibile

1. Che cos’è uno smoke test per ChatGPT App

Nel mondo dello sviluppo web “classico” uno smoke test è la verifica minima: «il sistema è vivo?». La pagina si apre, i pulsanti non vanno in crash, nulla di critico è in fiamme.

Nel mondo delle ChatGPT Apps lo smoke test è un po’ più interessante, perché nella catena partecipano subito diversi anelli:

  1. Il tuo codice del widget (React/Next.js).
  2. Il dev server di Next.js.
  3. Il tunnel (ngrok/Cloudflare).
  4. ChatGPT, che crea un iframe e carica il tuo widget dentro la chat.

Per noi un buon smoke test è la situazione in cui:

  • il widget viene renderizzato dentro ChatGPT senza errori;
  • l’interattività di base funziona (per esempio: clicchi un pulsante — si apre un link esterno);
  • né nella console del browser, né nei log del dev server c’è una valanga rossa di errori.

Importante: in questa fase non testiamo ancora gli strumenti MCP, non facciamo test di carico e non contiamo i costi dei token. Il nostro obiettivo è modesto e molto pratico: dimostrare che la catena «codice → Next.js → tunnel → ChatGPT → utente» si chiude davvero.

È utile immaginarlo come una tabella del genere:

Cosa verifichiamo Come capire che va tutto bene
Rendering del widget In ChatGPT si vede la nostra UI, non un «iframe rotto»
Connessione ChatGPT ↔ il nostro server Nessun errore del tipo «impossibile caricare l’applicazione»
Esecuzione JS nella sandbox I gestori onClick vengono realmente eseguiti
Possibilità di aprire un link esterno Il pulsante apre una nuova scheda/finestra con l’URL indicato

2. La nostra app didattica: un semplice «Hello GiftGenius»

In questo corso costruiamo gradualmente l’app GiftGenius — un assistente per la scelta dei regali. In questo passaggio non suggerisce ancora nulla, ma può almeno salutare educatamente e mostrare un link per «saperne di più».

Ci serve un widget minimo ma “vero”: senza logica complessa, ma con codice React reale.

La variante più semplice del componente del widget può apparire così (nome e stili li puoi adattare, ma prendiamo la base dal piano del corso):


// 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 }}>
        Questa è la tua prima ChatGPT App. Più avanti le insegneremo a suggerire regali.
      </p>
    </main>
  );
}

Un paio di punti importanti.

Per prima cosa, la direttiva 'use client'; all’inizio del file rende il componente client‑side. Senza di essa Next.js interpreta il file come componente server‑side, e non potrai usare window, i gestori onClick e più in generale nessuna API del browser.

In secondo luogo, è un normale componente React. Nessuna «magia dell’Apps SDK» è visibile qui — ed è onesto così. Tutta la magia del fatto che finisca dentro ChatGPT è nascosta nella configurazione del server MCP e dello strumento che restituisce il link all’URL del widget. Ce ne occuperemo più avanti; ora ci interessa solo la UI.

3. Integriamo il widget nel template e avviamo

Nel template ufficiale Next.js per l’Apps SDK la pagina del widget di solito esiste già; o la modifichi, oppure ne crei una tua sul percorso necessario (per esempio, /widget).

Supponiamo che tu abbia proprio app/widget/page.tsx e ne sostituisca il contenuto con il codice qui sopra. La catena poi è questa:

  1. Salvi il file.
  2. Il dev server di Next.js (già avviato con npm run dev) riavvia i moduli necessari, l’HMR aggiorna la pagina.
  3. Tramite il tunnel il tuo URL pubblico HTTPS sullo stesso percorso /widget comincia a servire la UI aggiornata.

Puoi verificarlo in due modi.

Prima, alla vecchia maniera — nel browser locale. Apri:

http://localhost:3000/widget

e vedi lo stesso Hello from GiftGenius. Sì, non è ancora ChatGPT: ti assicuri semplicemente che la UI della tua app Next.js sia viva.

Poi — tramite il tunnel. Prendi l’URL fornito (qualcosa come https://witty-cat.ngrok-free.app), aggiungi /widget e apri nel browser normale:

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

Se tutto va bene, la pagina deve apparire uguale. Significa che la catena «Next.js → tunnel → il tuo browser» funziona; resta solo inserire ChatGPT in mezzo.

4. Verifichiamo il widget dentro ChatGPT

In Dev Mode, ChatGPT in sostanza compie tre passi: crea un iframe, imposta in esso il src al tuo URL pubblico e lascia che questo iframe “viva” dentro il messaggio della chat.

In forma semplificata, l’evento appare così:

sequenceDiagram
    participant Dev as Tu (Dev)
    participant Next as Server di sviluppo Next.js
    participant Tun as Tunnel (HTTPS)
    participant GPT as ChatGPT
    participant User as Utente

    Dev->>Next: npm run dev (http://localhost:3000)
    Dev->>Tun: Avvio del tunnel verso la porta 3000
    GPT->>Tun: GET https://.../widget
    Tun->>Next: Proxy verso http://localhost:3000/widget
    Next-->>Tun: HTML + JS del widget
    Tun-->>GPT: Risposta con HTML/JS
    GPT->>User: Render dell'iframe con il widget

Per vedere il risultato, devi:

  1. Aprire ChatGPT nel browser, scegliere il modello desiderato (di solito GPT‑5.1 o quello impostato di default per il Dev Mode).
  2. Selezionare esplicitamente la tua applicazione (dal menu Apps/Developer) oppure “invocarla” con una frase tipo: «Avvia l’app GiftGenius».
  3. ChatGPT richiama la tua App, il server MCP restituisce una risposta che include il link alla UI (il famoso /widget) e nel messaggio della chat appare il tuo widget.

Se tutto va bene, vedi il familiare titolo «Hello from GiftGenius» direttamente dentro ChatGPT. A questo punto lo smoke test è quasi superato: l’iframe viene renderizzato, la catena «Next.js → tunnel → ChatGPT» è viva. Resta da verificare l’ultimo punto della nostra tabella: che il widget sappia aprire un link esterno in modo prevedibile. Per questo ci servirà openExternal.

Poco dopo, quando inizierai a cambiare il codice, il ciclo di sviluppo normale sarà così:

  1. Modifichi il JSX.
  2. Salvi.
  3. O ricarichi la scheda di ChatGPT, oppure (a volte) basta semplicemente “muovere” il widget — per esempio inviare un nuovo messaggio o avviare di nuovo l’App (dipende dal template e dalla cache).

Se le modifiche non si vedono, pensa subito a tre sospetti: il dev server non è avviato, il tunnel è caduto oppure ChatGPT è collegato a un URL vecchio. Nella sezione «Dove cercare gli errori se qualcosa è andato storto» analizzeremo questo scenario più nel dettaglio.

5. Perché non basta mettere <a href> e dimenticarsene

Per completare l’ultimo punto del nostro smoke test — un pulsante che apre una pagina esterna — dobbiamo affrontare openExternal. Domanda logica: «Ma perché serve openExternal? Che cosa impedisce di usare un link normale?»

Il problema è che il tuo widget non “vive semplicemente nel browser”, ma in un iframe gestito da ChatGPT. Questo iframe opera in una sandbox piuttosto rigida: possono valere restrizioni di Content Security Policy, attributi di sandbox, stranezze con target="_blank" e blocco dei popup. Di conseguenza, il comportamento di <ahref="…"> o di window.open() dentro un iframe del genere può risultare imprevedibile: dall’essere completamente ignorato fino alla comparsa di avvisi non controllati dal tuo codice.

Inoltre, dal punto di vista della UX OpenAI vuole controllare quando e come apri pagine esterne. Perciò l’Apps SDK fornisce un bridge unificato window.openai: il tuo codice non tocca direttamente la finestra padre, ma delega l’azione all’app host tramite un’API ben definita.

6. API window.openai.openExternal: che cos’è e come funziona

Nella sandbox del widget è disponibile l’oggetto globale window.openai. È il principale “bridge” tra la tua UI e ChatGPT: tramite esso puoi invocare strumenti, inviare follow‑up, cambiare modalità di visualizzazione, gestire lo stato del widget e, naturalmente, aprire link esterni.

In questa lezione ci interessa un metodo in particolare:

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

Quando chiami window.openai.openExternal({ href: 'https://example.com' }), ChatGPT:

  1. Verifica che l’URL sia consentito dalle policy.
  2. Può mostrare un avviso all’utente (per esempio che è un sito esterno).
  3. Apre il link in una nuova scheda/finestra del browser dell’utente.

È importante capire due cose.

Per prima cosa, è un’operazione puramente client‑side. Non invoca strumenti MCP, non parla con il tuo backend e non consuma token OpenAI. È solo un segnale all’app host: «per favore, apri questo URL».

In secondo luogo, questo approccio è compatibile con la sandbox. ChatGPT decide lui come aprire esattamente il link, senza permettere al tuo iframe di esagerare con window.open().

7. Aggiungiamo un pulsante con openExternal nel nostro widget

Ora impariamo ad aprire un link esterno dal nostro «Hello GiftGenius». Lo scenario più semplice: un pulsante «Apri il link di demo» che porta, per esempio, alla documentazione o al landing del tuo servizio.

Per iniziare scriviamo un piccolo helper, in modo che TypeScript non si lamenti e il widget non cada se apri /widget direttamente nel browser (dove window.openai non esiste ancora):

// 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 per la visualizzazione locale senza ChatGPT
    window.open(href, '_blank', 'noopener,noreferrer');
  }
}

Qui uso volutamente (window as any), per non appesantirti con la tipizzazione di window.openai. Più avanti nel corso descriveremo con cura l’interfaccia di questo oggetto. Per ora ci basta che il codice compili e funzioni.

Ora importiamo l’helper nel nostro widget e aggiungiamo il pulsante:

// 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 }}>
        Questa è la tua prima ChatGPT App. Più avanti le insegneremo a suggerire regali.
      </p>
      <button
        type="button"
        onClick={() => openExternalSafe('https://example.com')}
        style={{
          padding: '8px 16px',
          borderRadius: 8,
          border: '1px solid #ccc',
          cursor: 'pointer',
        }}
      >
        Apri il link di demo
      </button>
    </main>
  );
}

Cosa succede al click.

Se il widget è avviato dentro ChatGPT, window.openai.openExternal esiste e ChatGPT aprirà https://example.com come previsto dalle sue regole.

Se hai aperto http://localhost:3000/widget in un browser normale, window.openai non c’è e scatterà il fallback: si aprirà una nuova scheda con i meccanismi standard del browser. Qui window.open viene usato solo quando apri direttamente /widget in un browser, cioè non dentro la sandbox di ChatGPT. In questo contesto funziona normalmente e non crea problemi.

Analizzeremo openExternal in modo più dettagliato nel modulo 3 (lezione separata su widget e sandbox), quindi ora puoi tranquillamente passare all’avvio dell’applicazione.

8. Mini smoke test end‑to‑end

Ora possiamo fare un vero “giro di prova”. Proviamo a passare tutti i passi:

  1. Verifica che il dev server sia avviato (npm run dev) e che tu veda Hello from GiftGenius su http://localhost:3000/widget.
  2. Verifica che il tunnel verso la porta 3000 sia attivo e che l’URL pubblico si apra da un browser esterno.
  3. Apri ChatGPT, attiva il Dev Mode e assicurati che la tua App sia collegata all’URL corretto (pubblico, non localhost).
  4. Apri una chat, seleziona l’App (o chiedi al modello di avviarla).
  5. Assicurati che nel widget integrato si veda «Hello from GiftGenius».
  6. Clicca il pulsante «Apri il link di demo» e assicurati che nel browser si apra https://example.com (o il tuo indirizzo).

Se tutto questo ha funzionato, significa che:

  • L’HTML/JS del widget viene correttamente buildato e servito dal server Next.
  • Il tunnel HTTPS proxyta le richieste correttamente.
  • ChatGPT si fida del tuo URL e sa caricare il widget.
  • window.openai funziona e trasmette il comando di apertura del link esterno.

È esattamente ciò che volevamo dal primo smoke test.

9. Dove cercare gli errori se qualcosa è andato storto

A differenza del frontend “normale”, qui hai tre luoghi principali per la diagnosi. È importante capire in fretta in quale di essi si è rotto qualcosa:

  1. Per prima cosa guarda la UI in ChatGPT. Se al posto del widget vedi un messaggio di errore tipo «Error loading app» o «We had trouble talking to your app», il problema è con ogni probabilità nel tunnel o nella raggiungibilità del tuo dev server. Prova ad aprire l’URL pubblico direttamente nel browser: se non si apre o si apre con un errore di Next.js, sistemi questo per primo.
  2. Poi apri i DevTools del browser nella scheda dove gira ChatGPT. C’è un iframe separato per il tuo widget e al suo interno la solita scheda Console. Se cliccando il pulsante con openExternal non succede nulla, guarda se compaiono errori tipo «window.openai is undefined» o altri errori JS. Se c’è un errore del genere — probabilmente stai provando il widget non in ChatGPT (ma direttamente via URL del tunnel) oppure hai dimenticato la direttiva 'use client';.
  3. In parallelo guarda il terminale con npm run dev. Se lì compaiono errori di build (TypeScript, ESLint, compilazione), nel migliore dei casi ChatGPT vedrà una versione vecchia del codice, nel peggiore non vedrà nulla. Se non ci sono errori ma non vedi aggiornamenti, assicurati che il tunnel sia ancora attivo: molti servizi di tunneling chiudono le sessioni per timeout di inattività.

C’è anche un caso tipico: tutto funziona su localhost, ma tramite tunnel ottieni 404 o una pagina strana. Allora controlla con attenzione il percorso base (/widget vs /), le impostazioni basePath/assetPrefix (se le hai già toccate) e l’indirizzo impostato nel Dev Mode.

10. Un po’ di «pulizia»: arresto dei processi

È un dettaglio, ma nella pratica molto utile. I principianti spesso dimenticano che sia il dev server sia il tunnel sono processi separati che continuano a vivere in background.

Se all’improvviso «la porta 3000 è già occupata», è possibile che da qualche parte nei terminali si nasconda un vecchio npm run dev. Su Windows questo a volte si trasforma in “danze” attorno al task manager; su macOS e Linux salva Ctrl + C nel terminale dove il processo è in esecuzione.

Stesso discorso per il tunnel: se hai fatto esperimenti con più tunnel di seguito o hai dimenticato di chiuderne uno vecchio, è facile confondersi su quale URL sia attualmente collegata la tua App nel Dev Mode. Meglio crearsi l’abitudine: quando stai per chiudere la sessione — disattiva il tunnel, ferma il dev server e al prossimo avvio parti da un foglio pulito.

11. Errori tipici al primo smoke test

Errore n. 1: usare localhost invece di un URL pubblico HTTPS.
Capita spesso: in Dev Mode imposti per errore http://localhost:3000 o dimentichi del tunnel. Sulla tua macchina funziona tutto, ma ChatGPT, che vive nel cloud, fisicamente non può raggiungere localhost. La cura è semplice: verifica che nelle impostazioni dell’App sia indicato l’indirizzo pubblico HTTPS del tunnel, con il percorso corretto (/mcp o root — dipende dal template).

Errore n. 2: dimenticare la direttiva 'use client'; nel file del widget.
Scrivi un bel codice React, aggiungi onClick, accedi a window.openai, ma Next.js silenziosamente rende la pagina un componente server‑side. Nel migliore dei casi otterrai «window is not defined», nel peggiore il componente non verrà buildato. Per accedere alle API del browser, il widget deve essere un componente client, come indica la prima riga 'use client';.

Errore n. 3: chiamare direttamente window.open() invece di openExternal.
A volte sembra più semplice fare window.open('https://example.com'). In un browser normale può ancora funzionare, ma dentro la sandbox di ChatGPT otterrai un comportamento imprevedibile: dall’ignorare completamente fino al blocco. Il percorso giusto per le ChatGPT Apps è window.openai.openExternal({ href }), che delega l’apertura del link all’host e rispetta tutte le policy di sicurezza.

Errore n. 4: TypeScript si lamenta di window.openai e lo “si cura” disattivando i tipi.
A volte, disperati, si scrive // @ts-nocheck all’inizio del file. Questo elimina gli errori di compilazione, ma spegne anche tutto TypeScript in quel file. Molto più sicuro è usare un as any mirato intorno a window, oppure descrivere in un file separato un’interfaccia minima per window.openai. In questo modulo abbiamo scelto un piccolo helper openExternalSafe con (window as any), e aggiungeremo una tipizzazione accurata più avanti.

Errore n. 5: guardare il risultato solo in localhost e non dentro ChatGPT.
È allettante accontentarsi del fatto che http://localhost:3000/widget si apra e considerare il compito finito. Ma il senso di questo modulo è proprio vedere l’App dentro ChatGPT. Il fatto che nel browser normale tutto vada bene non garantisce che ChatGPT crei correttamente l’iframe, prenda le risorse tramite tunnel e non si imbatti in CORS/CSP. Uno smoke test completo include sempre il passo con l’avvio reale dell’App nell’interfaccia di ChatGPT.

Errore n. 6: tunnel dimenticato o caduto.
Hai aggiornato il codice, ma in ChatGPT rimane la vecchia versione del widget o non si carica nulla. Spesso si scopre che il tunnel si è chiuso per timeout, ma il Developer Mode punta ancora al vecchio URL. Se aprendo l’URL del tunnel in un browser normale vedi un errore — ripristina prima il tunnel, e solo dopo sospetta dell’Apps SDK.

Errore n. 7: ignorare la console nell’iframe.
Gli sviluppatori abituati alle SPA sono soliti guardare i console.log nei DevTools della propria app, ma dentro ChatGPT c’è un iframe e devi selezionare il frame giusto nei DevTools. Se guardi solo il livello superiore potresti non vedere alcun errore, anche se dentro il widget è tutto rosso. L’abitudine «aprire i DevTools proprio sull’iframe del widget» fa risparmiare molti nervi.

1
Sondaggio/quiz
La prima applicazione ChatGPT, livello 2, lezione 4
Non disponibile
La prima applicazione ChatGPT
La prima applicazione ChatGPT: template, Dev Mode, tunnel
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION