1. Анатомія розгортання: де взагалі може «ламатися»
Спершу корисно побачити весь ланцюжок цілком. Розгортання ChatGPT App у вашій архітектурі можна умовно розкласти на таку послідовність:
flowchart TD A[Ваш ноутбук
git commit] --> B[Git‑репозиторій
GitHub/GitLab] B --> C[Vercel Build
npm run build] C --> D[Vercel Deploy
Preview/Prod] D --> E[HTTP endpoint
/mcp, /api/...] E --> F[ChatGPT / Dev Mode
tool calls, widgets]
Помилка може зʼявитися на будь‑якому з цих кроків, але симптоми в ChatGPT зазвичай однакові: «Error talking to app», «Network error» або просто мовчання. Ваше завдання — не стріляти навмання, а спершу зрозуміти, де саме сталося падіння: на етапі збірки, під час виконання чи через те, що ChatGPT «дивиться» не туди.
Зручно ділити проблеми на три великі категорії:
- Build‑помилки: Vercel узагалі не зміг зібрати проєкт. Продакшн не оновився — і це навіть добре, зате ви бачите «червону» збірку.
- Runtime‑помилки: збірка пройшла, але під час запитів повертаються 500/502, є таймаути або дивна поведінка.
- Config drift (дрейф конфігурації): локально все гаразд, на Vercel за логами теж гаразд, але ChatGPT звертається до старого URL, працює зі старим маніфестом або з порожніми env‑змінними.
Ми пройдемо всі три шари й паралельно вибудуємо загальну стратегію налагодження.
2. Build‑помилки: коли проєкт не збирається
Це перший тип проблем зі вступу — build‑помилки: проєкт узагалі не збирається, тому що Vercel не може успішно зібрати ваш Next.js‑проєкт.
Node і Next.js: інше середовище — інші вимоги
Локально ви можете (на жаль) працювати із застарілою версією Node, а Vercel може спробувати зібрати ваш Next.js‑проєкт на версії 16, хоча підтримувана версія Node — щонайменше 18.18.0. Якщо в package.json явно вказано несумісну версію, збірка може зламатися в продакшні, хоча dev‑сервер у вас запускався.
Простий спосіб убезпечитися — у package.json явно вказати "engines":
{
"engines": {
"node": ">=18.18.0"
}
}
Тоді і локально, і в CI, і на Vercel ви заздалегідь побачите, що Node надто старий.
«У мене працює!» і забуті залежності
Класика: ви встановили бібліотеку через npm install some-lib, але не закомітили оновлений package-lock.json або ж частина залежностей у вас узагалі встановлена глобально. На Vercel застосунок збирається «з нуля»: він чесно виконує npm install на основі маніфесту, а вашої улюбленої some-lib там немає — отримуємо build error.
Тут допомагає жорстка дисципліна:
- будь‑які нові залежності додаються й одразу комітяться;
- перед push у main/production ви запускаєте npm run build локально. Якщо локальна збірка падає, на Vercel буде лише гірше.
Case‑sensitive файлова система
Локально багато хто працює на macOS або Windows, де файлова система за замовчуванням не розрізняє регістр у назві файла. На Vercel збірка йде в середовищі Linux: там Widget.tsx і widget.tsx — різні файли.
Типовий баг:
// Імпорт у коді
import { AppWidget } from "@/components/Widget";
// А в репозиторії лежить файл components/widget.tsx
На вашому компʼютері все працює, а на Vercel — помилка модуля «Cannot find module '@/components/Widget'». Лікується наведенням ладу в назвах і уважним ставленням до регістру.
Env‑змінні на етапі збірки
Ще одне джерело сюрпризів — використання process.env.* у коді, який виконується на етапі збірки (наприклад, у next.config.mjs або в модулях, імпортованих під час build). Якщо ви локально підхопили .env.local, а на Vercel забули завести ці змінні для build‑середовища, збірка або впаде, або — що ще неприємніше — пройде з undefined і «зашиє» невалідні значення в бандл.
Для ChatGPT App це особливо критично, якщо ви, наприклад, формуєте baseURL для MCP‑ендпойнта або URL зовнішніх API прямо на етапі збірки.
Добра практика — явно валідувати критичні env‑змінні ще до старту застосунку (про це поговоримо в окремому розділі), щоб збірка падала голосно й передбачувано.
3. Runtime‑помилки: коли все зібралося, але не працює
Тепер переходимо до другого шару зі вступу — runtime‑помилок: збірка пройшла, але під час виконання все ламається.
Збірка пройшла, Vercel радісно показав зелений деплой, ви перемкнули ChatGPT App на прод‑URL — і отримали в чаті «Error talking to app». Отже, проблема перейшла на рівень виконання.
Нульові або порожні env‑змінні
Найчастіше продакшн‑інцидент у світі ChatGPT App починається зі слова undefined. Локально у вас акуратний .env.local з OPENAI_API_KEY, MCP_BASE_URL та іншим, а на Vercel ви забули додати ці змінні або переплутали назви.
Наприклад, ви читаєте:
const apiKey = process.env.OPENAI_API_KEY;
а на Vercel завели OPENAI_APIKEY або OPENAI_API_KEY_PROD. У результаті під час першого ж виклику MCP‑інструмента ваш route‑handler падає з помилкою автентифікації.
Набагато приємніше, коли застосунок падає одразу й зрозуміло. Гарний патерн — окремий модуль у вашому Next.js‑проєкті, який валідує env‑змінні під час імпорту:
// app/lib/env.ts
const required = ["OPENAI_API_KEY", "MCP_BASE_URL"] as const;
type RequiredKey = (typeof required)[number];
function getEnv(key: RequiredKey): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing required env var: ${key}`);
}
return value;
}
export const env = {
OPENAI_API_KEY: getEnv("OPENAI_API_KEY"),
MCP_BASE_URL: getEnv("MCP_BASE_URL"),
};
Тепер, якщо ви забули налаштувати змінні на Vercel, Next.js впаде під час першого ж імпорту env, а в логах буде зрозуміле повідомлення «Missing required env var: …».
Важливо памʼятати: на Vercel зміни env‑змінних не підхоплюються автоматично. Після зміни значень потрібно зробити новий деплой (redeploy), інакше рантайм продовжить працювати зі старими значеннями.
Помилки в route‑handlerʼах і MCP‑ендпойнті
В офіційному шаблоні ChatGPT App MCP‑сервер зазвичай реалізовано як app/mcp/route.ts. Усередині — код, який розбирає JSON‑RPC‑запит, маршрутизує його до інструмента й повертає відповідь. Якщо десь у цьому ланцюжку викидається throw без обробки, користувач у ChatGPT отримає 500.
Варто завжди обгортати верхній рівень MCP‑хендлера в try/catch, логувати помилку й повертати структуровану відповідь:
// app/mcp/route.ts
import { NextRequest, NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export const maxDuration = 30; // секунд
export async function POST(req: NextRequest) {
try {
const body = await req.json();
// тут обробка MCP-запиту
const result = await handleMcpRequest(body);
return NextResponse.json(result);
} catch (error) {
console.error("MCP route error", error);
return NextResponse.json(
{ error: "Internal MCP error" },
{ status: 500 }
);
}
}
Кілька моментів:
- dynamic = "force-dynamic" допомагає уникнути неочікуваної статичної генерації та кешування для MCP‑маршрутів у Next.js 16.
- maxDuration = 30 явно повідомляє Vercel, що route‑handler може працювати до 30 с, що важливо під час довгих LLM‑запитів.
Таймаути й «Network error» у ChatGPT
Vercel обмежує час виконання serverless‑функцій: на безкоштовних тарифах це зазвичай близько 10 с, на платних може бути більше (до кількох хвилин). Якщо ваш MCP‑інструмент робить довгий запит до бази або зовнішнього API, він може не встигнути відповісти вчасно, і ChatGPT отримає «Network error» або обірваний стрим.
Якщо ви використовуєте стримінг (SSE) для часткових результатів, особливо важливо надіслати перші байти відповіді ще до завершення таймауту. Тоді сама передача може тривати довше, але платформа не вважатиме функцію «завислою».
Невеликий прийом: вимірюйте час виклику інструментів і логуйте його разом із назвою інструмента. Тоді в логах буде видно, що, наприклад, search_flights стабільно займає 12 с і трохи не вкладається в обмеження.
export async function safeToolCall<TInput, TOutput>(
name: string,
handler: (input: TInput) => Promise<TOutput>,
input: TInput
): Promise<TOutput> {
const started = Date.now();
try {
const result = await handler(input);
console.log("[tool] ok", name, { ms: Date.now() - started });
return result;
} catch (error) {
console.error("[tool] fail", name, {
ms: Date.now() - started,
error,
});
throw error;
}
}
Потім замість handler(args) ви викликаєте safeToolCall("search_flights", handler, args).
Мережа і зовнішні сервіси
Іноді вся справа в банальному https:// замість http:// або в застарілому baseURL. Особливо якщо спочатку ви тестували локально з одним URL, а в продакшні у вас уже інший домен або інший порт.
Корисно винести базові URL у конфігурацію (залежну від середовища) й не «вбудовувати» їх безпосередньо в код інструмента. Тоді під час зміни середовища ви змінюєте одну env‑змінну, а не згадуєте, у яких пʼяти місцях у коді у вас був http://localhost:3001.
4. Конфігурація і дрейф середовищ
І, нарешті, третій тип із нашої схеми — конфігураційний дрейф між середовищами.
Навіть якщо збірка пройшла, а runtime за логами «справний», ChatGPT може поводитися так, ніби працює не та версія застосунку. Це саме той випадок, коли проблема не стільки в коді, скільки в конфігурації та узгодженості середовищ.
Dev Mode проти продакшну
У Dev Mode (режимі розробника) ChatGPT звертається до Connector URL, який ви вказали вручну: зазвичай це тунельний URL (https://myapp-dev.ngrok-free.app/mcp або щось подібне) або staging‑URL на Vercel. У продакшні (через Store або налаштування організації) App має вказувати на стабільний прод‑ендпойнт, наприклад https://myapp.vercel.app/mcp.
Помилка, яку робить майже кожен: ви розгорнули застосунок на Vercel, але в налаштуваннях ChatGPT App досі вказано старий тунельний URL. Локальний сервер вимкнено, тунель давно «помер», а ChatGPT чесно звертається туди й отримує 502. В інтерфейсі це виглядає як «Error talking to app», і студент починає лагодити MCP‑код, який узагалі не виконується.
Лікується дисципліною: після будь‑якої зміни середовища (тунель → staging, staging → prod) ви перевіряєте, який саме URL прописано в Dev Mode і в прод‑конфігурації App.
Старий маніфест і кеш ChatGPT
ChatGPT кешує інформацію про ваш App: список інструментів, їхні описи, метадані. Тому ситуація «я змінив схему інструмента, а модель досі вважає, що аргумент називається по‑старому» — цілком реальна.
За суттєвих змін в інструментах корисно:
- переконатися, що ви справді розгорнули нову версію (подивитися commit hash у логах, вивести його в startup‑лог);
- створити заново або перепідʼєднати App у Dev Mode, щоб змусити платформу перечитати маніфест;
- на час налагодження працювати через MCP Inspector, де ви точно бачите актуальний список інструментів і схем.
Env‑конфіг: dev/staging/prod
Ми вже говорили про те, як env‑змінні можуть завалити збірку й runtime. Тут — погляд згори на dev/staging/prod та узгодженість значень між ними.
Поширений біль: .env.local у вас ідеальний, а у Vercel‑середовищах — «зоопарк». У підсумку:
- локально у вас один API‑ключ і один URL зовнішнього сервісу;
- на staging — узагалі інші значення;
- на prod — половину змінних не задано.
Дуже допомагає простий текстовий файл docs/env.md у репозиторії, де ви перелічуєте: які змінні потрібні, у яких середовищах вони обовʼязкові, які значення орієнтовні. Це може здаватися бюрократією, але під час інциденту такий список економить години.
5. Як виглядають помилки на боці ChatGPT
Тепер подивімося на ситуацію очима користувача ChatGPT. Він бачить лише інтерфейс і не знає нічого про Vercel, Node і MCP. А ви, на жаль, поки теж не знаєте, що саме там зламалося.
Типові симптоми в ChatGPT:
- повідомлення «Error talking to [App Name]» одразу після спроби використання;
- нескінченний індикатор завантаження без видимої помилки;
- червоний текст «I encountered an error while running the tool»;
- віджет не зʼявляється або зʼявляється порожнім.
Кожен із цих симптомів зазвичай відповідає певному рівню поломки:
- якщо App узагалі недоступний (не той URL, тунель упав, SSL‑помилка), ChatGPT не може достукатися до вашого MCP‑ендпойнта — перевіряйте доступність домену в браузері та логи Vercel з кодами 4xx/5xx;
- якщо MCP відповідає валідним JSON‑RPC з полем error, ChatGPT чесно пише, що інструмент повернув помилку — це вже питання до бізнес‑логіки або валідації аргументів;
- якщо MCP відповідає успішно, але у відповіді зламаний HTML віджета або є JS‑помилка, то в консолі віджета (DevTools → iframe віджета) буде видно, що саме впало.
Тому добра звичка така: щойно ви побачили дивну поведінку в чаті, одразу зафіксуйте мітку часу (timestamp) — щонайменше до хвилини — і йдіть у логи Vercel шукати запити за цей час.
6. Стратегія налагодження: як не панікувати, а діяти
Тепер зберемо з усього сказаного невелику «памʼятку» — сценарій дій, коли щось пішло не так. Мета — замінити біганину по колу на спокійний алгоритм.
Крок 1: визначаємо тип проблеми
Якщо збірка на Vercel червона — це навіть добре: помилку спіймано до продакшну. Відкриваємо build‑логи, дивимося першу реальну помилку (а не 200 рядків попереджень) і відтворюємо локально командою npm run build.
Якщо збірка зелена, а ChatGPT скаржиться, це runtime або конфігурація. Перевіряємо:
- чи доступний прод‑URL вашого App у браузері (https://myapp.vercel.app/mcp чи віддає хоч щось);
- чи повертає MCP‑ендпойнт 200/500 або взагалі не визначається;
- чи збігається URL у налаштуваннях App із тим, що ви щойно перевірили.
Крок 2: читаємо логи, а не ворожимо
Далі — логи Vercel: серверні логи для потрібного деплою та потрібного середовища (Preview/Production).
Шукаємо:
- помилки Error: Missing required env var ... — отже, проблема в конфігурації;
- stack trace із MCP‑хендлера — отже, падає бізнес‑логіка або парсинг вхідних даних;
- повідомлення про таймаути або перевищення тривалості роботи функції.
Паралельно не забуваємо про MCP Inspector. Якщо підʼєднатися до того самого MCP‑ендпойнта через інспектор і вручну викликати інструменти, швидко стане зрозуміло, проблема в самому MCP чи у звʼязці ChatGPT ↔ MCP.
Крок 3: швидкий rollback чи hotfix?
Якщо ви бачите, що прод‑деплой явно зламаний (наприклад, MCP route постійно кидає одну й ту саму помилку на кожен запит), а попередній деплой був стабільним, найрозумніше рішення — відкотитися. Vercel дозволяє швидко перемкнутися на попередній успішний деплой без перескладання — по суті, це зміна активної версії.
Це краще, ніж намагатися лагодити продакшн «на ходу», особливо якщо ви не до кінця розумієте причину інциденту.
Коли ситуацію стабілізовано, ви спокійно розбираєтеся з причиною, пишете тести, фіксуєте зміни в коді і вже потім розгортаєте наступну версію.
Крок 4: закріплюємо знання документацією
Будь‑який серйозний інцидент — привід оновити внутрішній README:
- додати до списку обовʼязкову env‑змінну, без якої все падає;
- зафіксувати, який саме кейс призвів до помилки (наприклад, «імпорт із неправильним регістром назв файлів»);
- описати короткий алгоритм дій, який допоміг швидко все виправити.
Це може здаватися нудним, але за кілька місяців ви самі скажете собі «дякую».
7. Невеликі практичні прийоми в коді
Тепер візьмемо кілька кроків із нашої памʼятки й закріпимо їх невеликими прийомами в коді нашого навчального застосунку (ChatGPT App).
Єдиний модуль конфігурації
Ми вже писали простий валідатор env‑змінних. Його можна доповнити, щоб розрізняти середовища:
// app/lib/config.ts
type NodeEnv = "development" | "test" | "production";
const nodeEnv = (process.env.NODE_ENV || "development") as NodeEnv;
const requiredBase = ["OPENAI_API_KEY"] as const;
const requiredProd = ["MCP_BASE_URL"] as const;
function ensure(keys: readonly string[]) {
for (const key of keys) {
if (!process.env[key]) {
throw new Error(`Missing env var ${key} for NODE_ENV=${nodeEnv}`);
}
}
}
ensure(requiredBase);
if (nodeEnv === "production") {
ensure(requiredProd);
}
export const config = {
nodeEnv,
openaiApiKey: process.env.OPENAI_API_KEY!,
mcpBaseUrl: process.env.MCP_BASE_URL ?? "http://localhost:3000/mcp",
};
Такий модуль одразу покаже вам, якщо продакшн запущено без потрібної змінної.
Логування вхідних MCP‑запитів
Проста, але дуже корисна обгортка для MCP‑хендлера:
// app/lib/mcp-logger.ts
export function logMcpRequest(body: unknown) {
console.log("[mcp] request", {
time: new Date().toISOString(),
// не пишемо в лог чутливі дані
keys: typeof body === "object" && body !== null
? Object.keys(body as Record<string, unknown>)
: typeof body,
});
}
І використовуємо в app/mcp/route.ts:
import { logMcpRequest } from "@/app/lib/mcp-logger";
export async function POST(req: NextRequest) {
try {
const body = await req.json();
logMcpRequest(body);
const result = await handleMcpRequest(body);
return NextResponse.json(result);
} catch (error) {
console.error("MCP route error", error);
return NextResponse.json({ error: "Internal error" }, { status: 500 });
}
}
У логах ви бачитимете, що взагалі приходить від ChatGPT: принаймні за ключами ("jsonrpc", "method", "params"), і вам буде легше зрозуміти, який саме виклик падає.
Проста перевірка доступності MCP‑ендпойнта
Іноді корисно мати невеликий route‑handler на кшталт «healthcheck» для MCP‑сервера, який ChatGPT безпосередньо не викликає, зате ви можете швидко відкрити його в браузері й зрозуміти, чи живий сервер і чи «бачить» він свої env‑змінні:
// app/api/health/route.ts
import { NextResponse } from "next/server";
import { config } from "@/app/lib/config";
export async function GET() {
return NextResponse.json({
status: "ok",
env: config.nodeEnv,
hasOpenAiKey: !!config.openaiApiKey,
});
}
Якщо https://myapp.vercel.app/api/health відповідає status: "ok", отже принаймні базовий пайплайн до вашого Node‑коду працює.
8. Типові помилки під час розгортання та налагодження
Помилка № 1: розгортання без локального npm run build.
Коли розробник ніколи не запускає збірку локально, він дізнається про несумісну версію Node, проблему зі шляхами або TS‑помилку лише на Vercel. Це подовжує цикл «зламав → виправив», тому що кожен експеримент — це новий деплой. Звичка проганяти npm run build перед push у main економить багато часу (див. також розділ 2 і крок 6.1 про локальний npm run build).
Помилка № 2: секрети залишилися лише в .env.local.
Застосунок чудово працює на машині автора, але в продакшні падає через process.env.OPENAI_API_KEY === undefined. Причина банальна: env‑змінні забули додати в налаштуваннях Vercel (а іноді їх ще й назвали інакше). Особливо часто забувають про поділ Development/Preview/Production і дивуються, що staging і prod поводяться по‑різному (докладніше — розділи 3.1, 4.3 і 7.1).
Помилка № 3: використання NEXT_PUBLIC_* для секретів.
У Next.js усі змінні з префіксом NEXT_PUBLIC_ потрапляють у браузерний бандл. Якщо ви з необережності назвали API‑ключ NEXT_PUBLIC_OPENAI_API_KEY, він поїде до користувача в браузер, і його можна буде витягнути з DevTools. Так робити не можна. Публічними мають бути лише безпечні значення (наприклад, ідентифікатори фіч‑флагів, але не токени).
Помилка № 4: ігнорування логів Vercel і спроби «виправити через ChatGPT».
Іноді розробник бачить у чаті «Error talking to app» і годинами змінює промпти, описи інструментів, щось править у Dev Mode, але жодного разу не заглядає в serverless‑логи. А там — цілком зрозуміла помилка: «Missing env var», «Cannot find module» або stack trace конкретного інструмента. Професійний інженер спершу дивиться логи, а вже потім сперечається з моделлю.
Помилка № 5: плутанина між Dev Mode і production‑App.
Після першого успішного деплою на Vercel легко забути, що Dev Mode, як і раніше, може «дивитися» на старий тунель або на preview‑URL. У результаті ви впевнені, що тестуєте продакшн‑версію, а насправді спілкуєтеся з локальною гілкою, яку давно час видалити. Або навпаки: думаєте, що тестуєте чернеткові правки, а ChatGPT стукається на бойовий endpoint. Потрібно регулярно перевіряти, який URL указано в налаштуваннях App і Dev Mode (див. також розділ 4.1 про Dev Mode і продакшн).
Помилка № 6: очікування, що зміна env‑змінної на Vercel запрацює «на ходу».
Деякі студенти змінюють значення змінних у панелі Vercel і одразу біжать у ChatGPT перевіряти результат. Але рантайм усе ще використовує старі значення, тому що не було redeploy. Будь‑яка зміна env‑змінних вимагає нового деплою, інакше функція не побачить оновлення (докладніше — розділ 3.1).
Помилка № 7: відсутність простої стратегії rollback.
У момент інциденту спокуса велика — «швидко запушити фікс» прямо в main. Але це додає ще один потенційно зламаний деплой, а користувачі весь цей час страждають. Набагато спокійніше мати звичку: за серйозної помилки одразу відкочуватися на попередній успішний деплой, виправляти проблему в окремій гілці й лише потім випускати нову версію. Vercel дає для цього зручний інтерфейс — гріх ним не користуватися.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ