1. Зачем нам Vercel для ChatGPT App
В прошлых лекциях мы запускали GiftGenius локально и подключали его к ChatGPT через Dev Mode и туннель. Теперь пора сделать ещё один шаг к «взрослому» продакшену и перенести тот же код на Vercel.
К этому моменту у вас уже есть рабочее приложение GiftGenius (наш условный учебный App). Локально оно крутится на Next.js 16 с MCP‑endpoint’ом (например, /api/mcp) и собрано на основе официального ChatGPT Apps SDK Next.js Starter.
Можно было бы пойти путём «я арендую VPS, руками ставлю Node, nginx и сам всё настраиваю», но для Next.js это примерно как писать фронтенд на чистом document.write в 2025 году. Работает, но вы явно усложняете себе жизнь.
Vercel хорош для нас по нескольким причинам.
Во‑первых, он нативно понимает Next.js: автоматически настраивает сборку, SSR, статику, edge‑слой и serverless‑функции. Для ChatGPT App это особенно удобно, потому что виджет и MCP‑endpoint раскатываются одной кнопкой и живут в одной инфраструктуре.
Во‑вторых, Vercel даёт CI/CD из коробки: вы подключаете Git‑репозиторий — и каждый push создаёт новый immutable deployment с уникальным URL. Из ветки main он считается production, из других веток — preview.
В‑третьих, у Vercel приятная история с окружениями и секретами. Он явно делит env‑переменные на Development, Preview и Production, хранит их зашифрованными и позволяет удобно прокидывать в Next.js. Это именно то, что нужно ChatGPT App, где ключи и URL MCP‑сервера должны меняться в зависимости от среды.
В‑четвертых, у Vercel есть удобные роллбэки: если новый релиз пошёл не так, можно быстро промоутить предыдущий успешный деплой и вернуть систему в рабочее состояние. Это снижает «страх деплоя» и поощряет маленькие частые релизы.
И, наконец, Vercel — это компания‑создатель Next.js. Они подогнали Next.js под свои сервера, а свои сервера под Next.js. Используя Vercel вы еще не раз прочувствуйте как плавно и в пару кликов все работает. Горантирую, вам понравится.
2. Исходная точка: структура проекта GiftGenius
По плану курса наш GiftGenius живёт в одном репозитории. Вариантов организации два, и оба допустимы для Vercel:
1) Монорепо с несколькими приложениями — например:
giftgenius/
apps/
web/ # Next.js (виджет + MCP)
mcp/ # отдельный MCP-сервер (если вы его вынесли)
2) Один Next.js‑проект, где и виджет, и MCP живут вместе (это проще на первом этапе и именно так устроен официальный стартер):
giftgenius/
app/
page.tsx # Виджет
api/
mcp/route.ts # MCP endpoint
next.config.mjs
package.json
...
В лекциях Модуля 2 вы уже клонировали Apps SDK Starter, ставили зависимости и запускали npm run dev. Сейчас мы предполагаем, что:
- проект уже в Git (GitHub / GitLab / Bitbucket);
- локально вы используете .env.local с ключами (OPENAI_API_KEY и др.);
- ChatGPT Dev Mode подключен к вашему туннелю.
Наша задача — сделать так, чтобы тот же код собирался и работал на Vercel, а ChatGPT мог ходить не к туннелю, а к стабильному HTTPS‑домену вида https://giftgenius.vercel.app.
3. Подготовка репозитория к деплою
Перед тем как жать в Vercel кнопку «New Project», стоит чуть привести репозиторий в порядок. Это простые шаги, но они сэкономят кучу времени позже.
Во‑первых, убедитесь, что .env.local и .vercel не попадают в репозиторий. В .gitignore у Next.js Starter это обычно уже есть, но лучше перепроверить:
node_modules
.next
.env.local
.vercel
.env.local — ваш локальный конфиг и секреты. Его никогда не должно быть в Git, особенно если там OPENAI_API_KEY или ключи к базе. На Vercel будем хранить секреты отдельно в UI.
Во‑вторых, посмотрите на package.json. Для Vercel важны корректные scripts:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
Vercel по умолчанию будет вызывать npm run build (или pnpm build, если вы используете pnpm). Это должно собирать проект без ошибок.
В‑третьих, удостоверьтесь, что версия Node указана и подходит для Next.js 16. В release notes Next.js 16 минимальная версия — 18.18.0. Чаще всего достаточно поля в package.json:
{
"engines": {
"node": ">=18.18.0"
}
}
Vercel подберёт LTS‑версию Node, совместимую с вашим приложением.
Если всё это сделано, можно пушить последний код в Git и переходить к Vercel.
4. Первый импорт проекта в Vercel
Теперь переходим в веб‑интерфейс Vercel. Если вы еще не зарегистрированы там — самое время.
Вы логинитесь в Vercel, нажимаете «New Project» и выбираете из списка свой репозиторий giftgenius. На этом этапе Vercel под капотом проверяет содержимое репозитория и почти всегда сам определяет, что это Next.js‑проект, подставляя соответствующий preset.
В настройках проекта Vercel предложит:
- Framework = Next.js;
- Build Command = npm run build (или pnpm build/yarn build);
- Output Directory — стандартная .next (не нужно менять).
Для первого деплоя можно не указывать env‑переменные сразу (мы их добавим отдельным шагом). Нажимаете «Deploy» — Vercel клонирует репозиторий, ставит зависимости, запускает npm run build и, если всё успешно, создаёт первый деплой с адресом вроде https://giftgenius-xyz.vercel.app.
Важно понять одну штуку: каждый деплой — immutable. Если вы потом пушите изменения, создаётся новый деплой с новым URL, а старый остаётся в истории. Продакшен‑домен (например, giftgenius.vercel.app или ваш кастомный домен) указывает на конкретный деплой, и его можно переключить назад, делая rollback.
Схематически это выглядит так:
flowchart LR
A[GitHub repo
giftgenius] -->|git push| B[Vercel build]
B --> C[Preview Deploy #1
unique URL]
B --> D[Preview Deploy #2
unique URL]
D --> E[Production Alias
giftgenius.vercel.app]
Гит‑ветка main обычно считается production‑веткой, всё остальное — preview. Но это можно перенастроить.
5. Переменные окружения на Vercel
Сейчас ваш первый деплой, скорее всего, не особо работоспособен: нет OPENAI_API_KEY, MCP‑сервер не ходит во внешние API и т.п. Пора заняться env‑переменными.
В Vercel env‑переменные хранятся в разделе Settings → Environment Variables. Там же видно разделение на три scope: Development, Preview и Production.
Табличка для ментальной модели:
| Scope | Где используется | Аналог локально |
|---|---|---|
| Development | vercel dev и локальный dev через Vercel CLI | .env.local |
| Preview | все деплои с веток, кроме production‑ветки | staging / test |
| Production | деплои из production‑ветки (обычно main) | «боевой» .env.prod |
Отличие от локальной .env.local в том, что Vercel хранит значения зашифрованными и автоматически подставляет их как process.env.MY_VAR в коде Next.js.
Очень важно понимать префикс NEXT_PUBLIC_. Всё, что начинается с NEXT_PUBLIC_, окажется в браузерном бандле и будет видимо любому пользователю (через DevTools можно посмотреть). Это хорошо для публичной конфигурации (NEXT_PUBLIC_ENV=preview, NEXT_PUBLIC_API_BASE_URL=https://giftgenius.vercel.app), но категорически плохо для ключей вроде OPENAI_API_KEY.
Для секретов мы используем имена без NEXT_PUBLIC_ и читаем их только на серверной стороне: в route handlers, MCP‑инструментах и т.д.
6. Настройка env для GiftGenius: пример
Давайте посмотрим, какие env‑переменные нужны нашему учебному GiftGenius.
Минимальный набор может выглядеть так:
- OPENAI_API_KEY — ключ для вызова моделей / MCP‑клиента;
- APP_BASE_URL — базовый URL приложения (https://giftgenius.vercel.app или preview‑URL);
- возможно, GIFTDATA_API_URL или PRODUCTS_API_URL, если у вас есть внешний каталог.
В локальной разработке это лежит в .env.local:
OPENAI_API_KEY=sk-local-...
APP_BASE_URL=http://localhost:3000
PRODUCTS_API_URL=https://dev-api.gifts.example.com
На Vercel вы идёте в Settings → Environment Variables и добавляете те же ключи и значения, только уже в соответствующие scope’ы.
Пример того, как это выглядит в коде MCP‑endpoint’а:
// app/api/mcp/route.ts
import { NextRequest } from 'next/server';
const apiKey = process.env.OPENAI_API_KEY!; // не делайте так без проверок в реальном коде :)
export async function POST(req: NextRequest) {
if (!apiKey) {
return new Response('Missing OPENAI_API_KEY', { status: 500 });
}
// Вызов OpenAI или другого сервиса с apiKey...
}
Виджет может использовать APP_BASE_URL на серверной стороне, например, чтобы строить абсолютные ссылки, учитывая ChatGPT‑iframe и конфигурацию assetPrefix/basePath из стартер‑шаблона.
Если ему нужен публичный URL API (например, для window.fetch к вашему backend’у), для этого можно завести NEXT_PUBLIC_API_BASE_URL. Но ни в коем случае не NEXT_PUBLIC_OPENAI_API_KEY.
7. Preview‑деплои: staging на стероидах
Теперь о самом приятном: preview‑деплоях. Когда вы подключили Git‑репозиторий, Vercel автоматически начинает создавать preview‑деплой для каждого пуша в не‑production ветку или для каждого Pull Request. У каждого такого деплоя есть уникальный URL, вроде:
https://giftgenius-git-feature-new-layout-username.vercel.app
Эти деплои используют scope Preview для env‑переменных, так что вы можете задать, например:
# Preview env на Vercel
APP_BASE_URL=https://giftgenius-staging.vercel.app
PRODUCTS_API_URL=https://staging-api.gifts.example.com
и не путать это с production.
С точки зрения ChatGPT Dev Mode preview‑URL — идеальный кандидат на staging. В настройках вашего Dev‑App вы можете временно поменять endpoint с туннельного URL на preview‑URL и посмотреть, как ведёт себя уже собранная версия GiftGenius, но ещё не production‑деплой.
Частый приём: для фичи вы создаёте ветку feature/smart-recommendations, пушите изменения — Vercel даёт preview‑ссылку. Вы идёте в Dev Mode, меняете URL на эту ссылку, проверяете сценарии с GPT (подбор подарка, отображение карточек, вызовы MCP‑инструментов). Только когда всё ок, мёржите в main. Production пока живёт своей спокойной жизнью.
Ментальная схема pipeline:
flowchart TD
A[Local dev
localhost + туннель] --> B[git push
feature/*]
B --> C[Preview Deploy
preview-URL]
C --> D[ChatGPT Dev Mode
App → preview-URL]
C --> E[Code review / тесты]
E --> F[Merge в main]
F --> G[Production Deploy
prod-URL]
G --> H[ChatGPT Prod App
App → prod-URL]
8. Production‑деплой и rollback
Когда вы мёржите изменения в main (или другую выбранную вами production‑ветку), Vercel создаёт production‑деплой и подвешивает на него production‑alias: giftgenius.vercel.app или ваш собственный домен.
В этот момент ChatGPT Prod‑App (который вы создадите чуть позже) должен быть настроен на production‑URL. В Dev Mode вы продолжаете экспериментировать с туннелем или preview‑URL; обычные пользователи в ChatGPT Store будут ходить именно в production.
Преимущество immutable‑деплоев в том, что rollback становится очень простым. Если новый релиз оказался неудачным (например, MCP‑инструмент падает на боевых данных), не нужно срочно чинить жизнь на проде. Вы открываете список деплоев в Vercel, выбираете предыдущий успешный и нажимаете что‑то вроде «Promote to Production» — K8s и Lambda где‑то далеко перетыкаются, а ваш домен снова указывает на стабильную версию.
В CLI это тоже можно автоматизировать через команды vercel rollback, но на уровне нашего курса достаточно понимать идею: каждый деплой — отдельный артефакт, а production‑alias можно направить на любой из них.
9. Специфика Next.js 16 + MCP на Vercel
С точки зрения Vercel ваш MCP‑endpoint в Next.js — это serverless‑функция (или edge‑функция, если вы так настроили). Она живёт недолго: просыпается по запросу, обрабатывает его и умирает. Состояние между вызовами хранить нельзя, если только вы не используете внешнюю БД или какое‑то хранилище.
Это критично для MCP: если вы вдруг решите складывать историю диалога в глобальный массив let history = [] в route.ts, он будет обнуляться на каждом холодном старте. Для хранения состояния нужно использовать внешнюю систему (KV, Postgres и т.д.), но это уже будущие модули.
Второй аспект — тайм‑ауты выполнения. На бесплатных планах Vercel serverless‑функции имеют ограничение по времени (на момент подготовки материалов — порядка 10 секунд на Hobby, больше на Pro). Для LLM‑запросов и особенно для цепочек MCP‑инструментов это может быть мало.
В Next.js 16 для route handlers можно задавать maxDuration, чтобы явно просить у Vercel больше времени (в пределах плана):
// app/api/mcp/route.ts
export const maxDuration = 60; // секунды, на Pro можно до 300
export async function POST(req: Request) {
// долгая операция: запрос к OpenAI, внешней БД и т.п.
}
Это не магическая кнопка «делай сколько угодно», но корректный способ сказать Vercel: «эта функция может работать подольше, пожалуйста, не убивай её слишком рано».
Наконец, не забывайте про особенности ChatGPT‑iframe. В Apps SDK Starter уже настроены assetPrefix и basePath, чтобы статика и маршруты корректно работали внутри nested‑iframe’ов web-sandbox.oaiusercontent.com. Благодаря этому все запросы идут на ваш домен, а не на sandbox. При деплое на Vercel этот конфиг сохраняется, так что вы получаете корректную работу виджета из коробки.
10. Интеграция с ChatGPT после деплоя
Хотя формально это ближе к модулям про Store и продакшен, жизнь приложения, логика интеграции с ChatGPT после деплоя довольно проста и хорошо «ложится» уже сейчас.
Сначала вы деплоите GiftGenius на Vercel и получаете production‑URL. Потом в ChatGPT в Dev Mode создаёте отдельный App, например GiftGenius Prod, и в его настройках как endpoint указываете этот URL (точнее, MCP‑endpoint вроде https://giftgenius.vercel.app/api/mcp в соответствии с гайдом OpenAI Apps SDK Deploy).
Для разработки вы продолжаете использовать Dev App, смотрящий на туннель или preview‑URL. Для тестирования дневных/недельных билдов можно завести Staging‑App, привязав его к постоянному preview‑alias. В результате получается трёхступенчатая схема:
Dev App → локальный туннель или dev-URL (нестабилен)
Staging App → стабильный preview/staging URL на Vercel
Prod App → production URL на Vercel
Для ориентира сведём всё в одну табличку:
| Что | URL / деплой на Vercel | Scope на Vercel | Кто ходит |
|---|---|---|---|
| Dev App | локальный туннель / vercel dev | Development | вы / команда |
| Staging App | стабильный preview‑alias | Preview | команда / QA |
| Prod App | giftgenius.vercel.app / кастомный домен | Production | пользователи |
Это та же модель local / staging / prod, о которой говорили в начале модуля, только теперь с привязкой к Vercel и ChatGPT Apps. Это уже архитектура взрослого проекта, а не вечный localhost.
11. Типичные ошибки при деплое на Vercel
Ошибка №1: секреты остались только в .env.local, а на Vercel их нет.
Очень частый сценарий: локально всё работает, вы уверенно жмёте «Deploy», приложение собирается, но MCP‑инструменты в продакшене возвращают 500 с текстом «Missing OPENAI_API_KEY». Причина проста: Vercel не знает про ваши локальные .env.local. Нужно отдельно занести те же переменные в настройки проекта на Vercel (и в нужные scope’ы: Preview, Production).
Ошибка №2: использование NEXT_PUBLIC_ для чувствительных данных.
Желание «просто чтобы работало» иногда побеждает, и разработчик пишет NEXT_PUBLIC_OPENAI_API_KEY, чтобы иметь доступ к ключу из клиентского кода. В результате ключ оказывается в JS‑бандле и доступен любому пользователю. Это не просто плохая практика, это прямой путь к утечке и блокировке ключа. Все секреты — только без префикса и только на серверной стороне.
Ошибка №3: несогласованность окружений между локалкой и Vercel.
Локально у вас может быть один URL для продуктов (http://localhost:4000), на Vercel — другой (https://api.gifts-staging.com), а в проде — третий. Если не вести аккуратный список env‑переменных и не проверять, что в Preview/Production они заполнены корректно, легко получить ситуацию, когда продакшен‑виджет ходит на staging‑бэкенд, а staging‑виджет — на прод. Помогает простая дисциплина: документировать все нужные переменные и проверять их на каждом окружении.
Ошибка №4: игнорирование лимитов по времени выполнения для MCP‑endpoint’ов.
На локалке вы можете ждать ответ от какой‑нибудь медленной внешней системы 30 секунд и не заметить проблем. На Vercel та же функция получит тайм‑аут через 10–15 секунд, и ChatGPT увидит ошибку. Если вы не настроили maxDuration и не следите за временем работы MCP‑инструментов, в проде это может превращаться в случайные падения.
Ошибка №5: попытка хранить состояние MCP в памяти serverless‑функции.
Иногда очень хочется положить историю диалога или кеш рекомендаций в глобальную переменную let cache = {} прямо в файле route handler’а. На локалке, пока dev‑сервер крутится долго, это может даже «работать». Но на Vercel каждая serverless‑функция живёт недолго и часто пересоздаётся. В результате часть запросов «видит» старый cache, часть — новый, а часть — пустой. Это порождает странные баги, которые тяжело воспроизвести. Для состояния нужна внешняя БД или KV‑хранилище; на уровне этой лекции лучше вообще считать MCP‑endpoint stateless.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ