JavaRush /Курсы /ChatGPT Apps /Туннель во взрослой жизни: стабильные dev‑URL и обновлени...

Туннель во взрослой жизни: стабильные dev‑URL и обновление в Dev Mode

ChatGPT Apps
7 уровень , 2 лекция
Открыта

1. Проблема «рандомных» туннелей

Когда вы первый раз запускаете ngrok http 3000 или быстрый Cloudflare Quick Tunnel, это ощущается как магия: бац — и ваш http://localhost:3000 превратился в https://random-1234.tunnelprovider.com. Можно скопировать URL в ChatGPT Dev Mode, и GPT радостно грузит ваш App.

А потом вы перезапускаете туннель… и получаете новый домен. Старый URL в настройках Dev‑приложения в ChatGPT внезапно превращается в «битую ссылку», GPT честно пишет «App unavailable», а вы снова идёте в настройки, меняете URL, жмёте Save, ждёте, пока оно там обновится, и молча ненавидите весь этот стек.

Для одноразового «поиграться вечером» это терпимо. Но когда вы:

  • каждый день допиливаете App;
  • хотите показать промежуточную версию коллеге/менеджеру;
  • параллельно заводите ещё staging и production,

переподключение Dev Mode под каждый новый случайный URL становится чистым страданием.

Плюс, если вы уже добавили в приложение что‑то, зависящее от домена (например, OAuth redirect URI или вебхуки), каждый новый URL ломает и их тоже. Появляется каскад: поменял туннель — и приходится править конфигурацию App, redirect‑URL в OAuth‑провайдере и настройки webhook‑приёмника.

Из этого вырастает ключевая идея лекции: стабильный dev‑URL — это не роскошь, а средство сохранения психического здоровья разработчика.

Insight

У ChatGPT есть очень жёсткие таймауты на работу с вашим приложением, и их легко недооценить. MCP-tool-call имеет лимит по времени: максимум 2 минуты — после этого платформа просто считает вызов неудачным, даже если ваш сервер всё ещё что-то делает.

Ещё строже обстоят дела с регистрацией приложения (Store или Dev Mode): на чтение манифеста, ресурсов и описаний инструментов ChatGPT даёт около 20 секунд. Если за это время ваш MCP-сервер не успел инициализироваться, отдать список tools/resources и т.п, регистрация App отвалится по timeout.

Рекомендация: вся тяжёлая инициализация должна происходить до того, как вы идёте в Dev Mode или Store. Прогрев соединений с БД, загрузка больших конфигов, ленивые кэши — всё это лучше выполнить заранее, например, один раз дёрнув сервер через MCP Jam или отдельный внутренний скрипт. С точки зрения платформы MCP-сервер обязан быть «тёплым» и отвечать за секунды, а не «просыпаться» во время регистрации.

2. Что такое «взрослый» туннель

Давайте зафиксируем, чем «взрослый» туннель отличается от того, что вы запускали в начале курса.

Ранний режим (модуль 2) выглядел так:

# Пример на ngrok
ngrok http 3000
# Получаем: https://random-abc123.ngrok-free.app

Вы брали этот одноразовый URL и вставляли в Dev Mode. При следующем запуске ngrok URL уже другой, и конфигурация ChatGPT устарела.

В «взрослом» подходе у вас:

  • есть статический поддомен у туннель‑провайдера (или свой домен);
  • один и тот же домен всегда прокидывается на ваш localhost:3000;
  • вы можете перезапускать туннель, машину, роутер, но URL остаётся прежним.

Такие статические поддомены доступны, например:

  • в ngrok — бесплатный static domain на аккаунт;
  • в Cloudflare Tunnel — через именованный туннель и привязку к своему домену.

А ChatGPT‑приложение в Dev Mode настроено ровно на этот один URL и больше о вас не беспокоит.

С формальной точки зрения требования к нашему «взрослому» туннелю такие:

  • стабильно один и тот же публичный HTTPS‑домен;
  • валидный TLS‑сертификат (провайдер делает это за нас);
  • конфиг, который описывает: «всё, что пришло на https://dev.yourdomain.com, прокинь на http://localhost:3000»;
  • опционально — минимальные меры безопасности (хотя бы не светить URL на StackOverflow).

3. Настраиваем стабильный dev‑URL: пример с Cloudflare Tunnel

В курсе мы рекомендуем Cloudflare Tunnel как основной инструмент, потому что он хорошо ложится и на dev, и на более серьёзные сценарии. Уже в модуле 2 вы видели пример базовой настройки, теперь «доворачиваем» это до постоянного dev‑URL.

Предположим, у нас учебное приложение GiftGenius, и мы хотим стабильный URL giftgenius-dev.yourdomain.com.

Минимальные шаги (упрощённо, без привязки к UI Cloudflare):

  1. Привязываем домен к аккаунту Cloudflare (один раз, через их панель).
  2. Ставим cloudflared локально и логинимся.
brew install cloudflare/cloudflare/cloudflared   # macOS
cloudflared login                                # откроет браузер для авторизации

3. Создаём именованный туннель:

cloudflared tunnel create giftgenius-dev

4. Настраиваем маршрут в ~/.cloudflared/config.yml:

tunnel: giftgenius-dev
credentials-file: /Users/you/.cloudflared/giftgenius-dev.json

ingress:
  - hostname: giftgenius-dev.yourdomain.com
    service: http://localhost:3000  # наш Next.js dev-сервер
  - service: http_status:404

5. Запускаем туннель:

cloudflared tunnel run giftgenius-dev

Теперь, пока работает npm run dev и cloudflared tunnel run, ваш локальный Next.js доступен по постоянному URL https://giftgenius-dev.yourdomain.com. И именно его вы указываете в настройках ChatGPT Dev Mode.

Как это вяжется с нашим приложением

Если вы откроете в браузере URL вашего приложения, который вы вводите в ChatGPT при подключении Dev‑приложения:

https://giftgenius-dev.yourdomain.com/mcp

вы увидите ответ (ошибку) — что‑то типа такого:

{"jsonrpc":"2.0","error":{"code":-32000,"message":"Method not allowed."},"id":null}

Это абсолютно нормально, ведь сервер по /mcp не ждёт GET-запрос. Все остальные части приложения — виджет, MCP endpoint /mcp, API роуты — едут этим же туннелем, вам не нужно каждый раз помнить новый домен.

4. Альтернатива: стабильный поддомен в ngrok

Если вы уже привыкли к ngrok, его можно «повзрослить» примерно так же, используя static domain. Начиная с 2023 года ngrok даёт даже на бесплатном плане возможность закрепить один статический поддомен вида myapp-dev.ngrok-free.app.

Минимальная схема:

# ~/.config/ngrok/ngrok.yml
authtoken: <ваш токен>
tunnels:
  giftgenius-dev:
    addr: 3000
    proto: http
    domain: giftgenius-dev.ngrok-free.app

Запуск:

ngrok start giftgenius-dev

В результате URL https://giftgenius-dev.ngrok-free.app будет постоянным, и его же вы даёте ChatGPT Dev Mode как базовый URL приложения.

Философия та же:

  • никаких «рандомных» адресов;
  • изменился только внутренний статус туннеля (запущен/не запущен), а не домен;
  • Dev Mode не нужно переподключать.

Cloudflare и ngrok в этом смысле просто разные вкусы мороженого. Некоторым нравятся свои домены и «тонкий» контроль DNS (Cloudflare), другие предпочитают «завёл YAML — готово» (ngrok). Для курса оба подхода валидны, главное — стабильный URL.

5. Схема: ChatGPT Dev Mode ↔ туннель ↔ локальный стек

Чтобы чуть формализовать происходящее, нарисуем картинку.

flowchart TD
    ChatGPT["ChatGPT (Dev Mode)"]
    AppCfg["Dev App (конфиг: httрs://giftgenius-dev...)"]
    Tunnel["Туннель Cloudflare/ngrok (giftgenius-dev...)"]
    Next["Next.js dev-сервер localhost:3000 + MCP handler"]

    ChatGPT --> AppCfg
    AppCfg -->|"в конфиге указан https://giftgenius-dev.../.well-known/openai-app"| Tunnel
    Tunnel -->|"HTTPS → HTTP проксирование"| Next

ChatGPT никогда не знает, что вы там крутите на ноутбуке. Для него есть только один HTTPS‑эндпоинт. Что именно за ним — Vercel, локальный туннель, Kubernetes — уже ваше дело. И в этой лекции нас интересует именно стабильность этого HTTPS‑эндпоинта для локальной разработки.

Осталось сделать так, чтобы внутри нашего приложения этот адрес тоже был единым «источником правды», а не разъезжался по хардкоженным строкам — этому и посвящён следующий раздел.

6. Переменные окружения и baseURL в коде

Чтобы всё это работало без сюрпризов, полезно один раз определить в коде Next.js «базовый внешний URL приложения» и дальше опираться только на него.

Например, в папке app/lib/config.ts в нашем приложении GiftGenius можно завести:

// app/lib/config.ts
export const baseUrl =
  process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; // fallback

export const mcpEndpoint = `${baseUrl}/mcp`;  // URL MCP-сервера

А в .env.local при разработке указать:

NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com

Тогда:

  • внутри виджета и любых ссылок вы всегда используете baseUrl;
  • для ChatGPT Dev Mode и браузера всё выглядит консистентно;
  • если завтра вы переедете на Vercel staging с доменом https://giftgenius-staging.vercel.app, достаточно поменять только переменную окружения.

Это особенно важно для:

  • callback‑URL (например, для OAuth, webhook‑обработчиков);
  • ссылок, которые вы показываете пользователю в виджете (кнопка «Открыть в браузере» через openExternal);
  • любых абсолютных URL в логике приложения.

Пока мы говорим только про dev‑URL, но сама архитектурная идея «один источник правды для baseUrl» отлично работает и спокойно переедет с вами и на staging/production.

7. Обновление URL в ChatGPT Dev Mode

Ну окей, мы сделали себе красивый стабильный домен. Как теперь с ним жить в Dev Mode?

Логика такая:

  1. В настройках Dev‑приложения вы один раз указываете корневой URL: https://giftgenius-dev.yourdomain.com/
  2. ChatGPT ходит по нему за манифестом (.well-known/openai-app) и дальше использует те же корни для обращения к MCP (/mcp), статике и т.д.
  3. Если вы меняете только код (React‑виджет, MCP‑handlers, стили), URL менять не нужно вообще. Достаточно, чтобы туннель был запущен и Next.js‑сервер отвечал.
  4. Если вы меняете сам домен (редко, например с ngrok на Cloudflare), нужно один раз зайти в Dev Mode и поменять endpoint.

В некоторых случаях ChatGPT кэширует манифест, и изменения в нём могут появляться не мгновенно. В интерфейсе Dev Mode обычно есть кнопка вроде «Reload configuration / Refresh App», но в худшем сценарии поможет даже тривиальное «отключить и заново подключить App по тому же URL».

Важно: пока вы не меняете URL, Dev Mode автоматически «подхватывает» новые версии кода. Главный триггер для App — домен, а не commit‑hash.

8. Переключение между dev / staging / prod в Dev Mode

Стабильный dev‑домен — это только первая ступень. Чтобы не утонуть в хаосе URL‑адресов по мере роста проекта, полезно сразу понимать, как dev‑туннель вписывается в общую схему окружений (dev/staging/prod) и Dev Mode. Хотя staging и prod — тема скорее следующей лекции про Vercel, Dev Mode уже сейчас умеет работать с несколькими окружениями.

Для понимания рассуждений дальше полезна такая таблица:

Окружение Базовый URL Где крутится код
Local
https://giftgenius-dev.yourdomain.com
Локальный Next.js + MCP через туннель
Staging
https://giftgenius-staging.vercel.app
Vercel Preview / staging‑деплой
Prod
https://giftgenius.vercel.app
Vercel Production

Варианта работы с Dev Mode два.

Первый — один Dev‑App, но вы время от времени обновляете в его настройках URL, чтобы потыкать staging или prod (аккуратно). Такой подход годится на ранних стадиях, но его легко перепутать: сегодня вы тестировали локалку, завтра staging, послезавтра забыли переключить и случайно гоняете через Dev‑App запросы в прод.

Второй — более здоровый: несколько Dev‑приложений, каждое с чёткой привязкой к окружению:

  • GiftGenius Devgiftgenius-dev.yourdomain.com;
  • GiftGenius Staginggiftgenius-staging.vercel.app;
  • GiftGenius (боевой, через Store) → giftgenius.vercel.app.

В рамках этой лекции мы делаем шаг за шагом, наводя порядок хотя бы в dev‑URL. В следующей лекции вы увидите, как логично привязать Vercel и preview‑деплои к staging/production.

9. Работа в команде: несколько разработчиков и один туннель

Пока у нас один dev‑домен и один разработчик‑интроверт, туннель — ваш личный друг. Но как только в проект приходит команда, туннели и окружения начинают пересекаться, и здесь важно не устроить «войну за один поддомен».

Представим, что два разработчика решили использовать один и тот же статический поддомен, скажем giftgenius-dev.ngrok-free.app. Оба запускают у себя ngrok start giftgenius-dev. В лучшем случае один туннель просто не поднимется (конфликт домена), в худшем — вы будете по очереди «перебивать» друг другу сессию, и ChatGPT то попадает к одному, то к другому.

Здесь есть несколько стратегий.

Самая простая — персональные dev‑домены:

  • alex.dev.giftgenius.app;
  • maria.dev.giftgenius.app.

И каждому свой Dev‑App в ChatGPT, например GiftGenius Dev (Alex) и GiftGenius Dev (Maria). Тогда каждый спокойно крутит локалку и не мешает соседу.

Более «командный» путь — общий staging‑endpoint:

  • У всех разработчиков есть личный dev‑туннель (для личной отладки).
  • Плюс есть staging на Vercel, куда мержатся feature‑ветки и куда смотрит общий Dev‑App GiftGenius Staging.

Такой подход часто встречается в реальных командах:

  • фича рождается на локалке и отлаживается через личный туннель;
  • после pull‑request и мёрджа она тестируется всеми через staging (без туннелей, просто через Vercel URL).

10. Безопасность dev‑туннеля (коротко и без паранойи)

Туннель — это удобный способ вытащить ваш локальный сервер в интернет. А интернет, как мы знаем, в основном состоит из ботов, сканеров и людей, которые любят проверять, не забыли ли вы пароль admin/admin.

Базовые вещи, о которых стоит помнить уже на этапе dev:

  • туннель даёт внешний доступ ко всему, что висит на этом порту; не поднимайте туда ещё и админку БД, phpMyAdmin и «мою тестовую CRM без пароля»;
  • не публикуйте URL туннеля в открытых репозиториях и чатах;
  • выключайте туннель, когда не работаете (и ноутбук тоже иногда выключайте — ему тоже нужен отдых).

Более серьёзные меры вроде Basic Auth, проверки спец‑заголовков или токена в URL мы будем разбирать уже в модулях про безопасность. Важно сейчас лишь одно: туннель — инструмент разработки, а не защищенный сервер. Продакшен будет жить на нормальном хостинге, вроде Vercel, с другими механизмами защиты.

11. Практика: настраиваем стабильный dev‑URL для нашего приложения

Теперь от теории и предостережений — к практике: давайте привяжем всё это к нашему учебному приложению на Next.js (шаблон Apps SDK).

Предположим, структура проекта выглядит так:

apps/
  web/          # Next.js App + виджет
  mcp-server/   # (опционально) отдельный MCP, или /mcp хэндлер в web

На практике вы можете держать MCP прямо в Next.js, в app/api/mcp/route.ts, но принцип тот же.

Шаг 1. Правим .env.local

Добавим туда стабильный dev‑URL туннеля:

NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com

В дев‑коде мы уже использовали baseUrl из этой переменной (см. выше). Если не использовали — самое время выделить.

Шаг 2. Запускаем dev‑сервер и туннель

cd apps/web
npm run dev          # Next.js на localhost:3000

# отдельный терминал
cloudflared tunnel run giftgenius-dev

Проверяем в браузере, что https://giftgenius-dev.yourdomain.com открывается и показывает ваш App.

Шаг 3. Подключаем в ChatGPT Dev Mode

В интерфейсе ChatGPT (секция для разработчиков):

  • создаём или редактируем GiftGenius Dev;
  • в поле URL/Endpoint указываем https://giftgenius-dev.yourdomain.com/;
  • сохраняем.

После этого ChatGPT обращается к манифесту по пути /.well-known/openai-app, затем начинает запускать ваш App поверх этого домена.

Теперь вы можете:

  • менять код виджета, MCP‑handlers, стили;
  • перезапускать npm run dev;
  • перезапускать cloudflared tunnel run giftgenius-dev;

и при этом никогда больше не лазить в настройки Dev‑App, пока домен остаётся тем же.

12. Как это выглядит в логике кода: пример с openExternal

Чтобы замкнуть пример на то, что мы уже писали в предыдущих лекциях, добавим в виджет кнопку «Открыть полный интерфейс в браузере», которая тоже будет использовать стабильный dev‑URL.

Предположим, у нас есть React‑компонент виджета GiftWidget:

// app/components/GiftWidget.tsx
"use client";

import { baseUrl } from "../lib/config"; // берём baseUrl из env

export function GiftWidget() {
  const handleOpenFull = () => {
    window.openai.openExternal({
      // открываем страницу приложения в отдельной вкладке
      url: `${baseUrl}/full`,
      label: "Открыть полный интерфейс",
    });
  };

  return (
    <div>
      <button onClick={handleOpenFull}>
        Полный режим
      </button>
    </div>
  );
}

Если NEXT_PUBLIC_APP_URL указывает на туннель, то:

  • при локальной разработке откроется страница https://giftgenius-dev.yourdomain.com/full;
  • после деплоя на staging — https://giftgenius-staging.vercel.app/full;
  • в prod — боевой домен.

И снова — один источник правды для домена: меняем окружение, но не меняем код.

13. Мини‑стратегия: как думать про туннель «по‑взрослому»

Сводя всё в простую ментальную модель, можно считать, что:

  • туннель — это просто временный провод между вашим ноутбуком и стабильным публичным доменом;
  • ChatGPT Dev Mode знает только домен, и ему всё равно, где физически работает код;
  • чем реже вы меняете домен, тем меньше времени проводите в настройках ChatGPT и OAuth‑провайдеров;
  • dev‑туннель — это лишь одна строчка в вашей общей карте окружений, рядом со staging (Vercel preview) и prod (Vercel production).

Следующая лекция как раз покажет, как этот провод заменяется полноценным хостингом на Vercel, и как связать всё это с Git‑ветками, preview‑деплоями и продакшеном.

14. Типичные ошибки при работе со «взрослым» туннелем

Ошибка №1: «Я настроил статический домен, но всё равно продолжаю использовать рандомные URL».
Иногда разработчик один раз делает красивый giftgenius-dev.yourdomain.com, но по привычке запускает ngrok http 3000 без конфига. В результате ChatGPT смотрит на один домен, а код крутится за другим. Если вы уже сделали стабильный dev‑URL — используйте только его и запускайте туннель через конфиг (именованный туннель/профиль).

Ошибка №2: Жёстко захардкоженный localhost:3000 в коде.
Встречается, когда в React‑компоненте или MCP‑обработчике пишут fetch("http://localhost:3000/api/..."). Локально ещё как‑то работает, но в Dev Mode и тем более на staging/prod ломается моментально. Всегда выносите базовый URL в конфиг (baseUrl, NEXT_PUBLIC_APP_URL) и используйте его везде, где нужны абсолютные ссылки.

Ошибка №3: Постоянная правка URL в Dev Mode вместо стабильного туннеля.
Если вы ловите себя на мысли «ну ладно, ещё раз поменяю URL в настройках, чего уж там», — это звоночек. Настройка статического поддомена в ngrok/Cloudflare занимает один раз 10–15 минут, зато экономит часы по ходу разработки.

Ошибка №4: Общий статический домен на команду без правил.
Два разработчика, один домен giftgenius-dev.ngrok-free.app, и оба запускают туннель, когда захотят. Результат — конфликт туннелей, «мистически» пропадающие ответы в Dev Mode и отладка в стиле «у меня же работало». Для команды всегда либо личные dev‑домены, либо один staging‑домен на реальном хостинге.

Ошибка №5: Туннель как «почти продакшен».
Иногда кто‑то додумывается: «Ну если у меня стабильный HTTPS‑URL через туннель, давайте пускать через него настоящих пользователей/платежи». Это путь к боли: ноутбук выключили — приложение умерло, интернет отвалился — то же самое, а безопасность в лучшем случае символическая. Туннель — это dev‑инструмент. Для боевого трафика предназначены Vercel и прочая взрослая инфраструктура, до которой мы доберёмся в следующей лекции.

Ошибка №6: Забытая синхронизация переменных окружения и Dev Mode.
Часто меняют NEXT_PUBLIC_APP_URL в .env.local, но забывают изменить URL в Dev Mode (или наоборот). В итоге виджет генерирует ссылки на один домен, а ChatGPT обращается к другому. Держите простую табличку «окружение ↔ домен ↔ App в ChatGPT» и обновляйте её при изменениях — это дешевле, чем гадать, какой URL сейчас настоящий.

1
Задача
ChatGPT Apps, 7 уровень, 2 лекция
Недоступна
Страница “Dev Base URL” (один источник правды)
Страница “Dev Base URL” (один источник правды)
1
Задача
ChatGPT Apps, 7 уровень, 2 лекция
Недоступна
Warm-up скрипт перед Dev Mode (таймауты и “прогрев”)
Warm-up скрипт перед Dev Mode (таймауты и “прогрев”)
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ