CodeGym /Các khóa học /ChatGPT Apps /Triển khai trên Vercel: kho Git, biến môi trường (env), p...

Triển khai trên Vercel: kho Git, biến môi trường (env), preview → production

ChatGPT Apps
Mức độ , Bài học
Có sẵn

1. Tại sao chúng ta cần Vercel cho ChatGPT App

Ở các bài trước, chúng ta chạy GiftGenius cục bộ và nối nó với ChatGPT qua Dev Mode và tunnel. Bây giờ đã đến lúc tiến thêm một bước tới production bài bản và đưa đúng đoạn mã đó lên Vercel.

Tại thời điểm này, bạn đã có một ứng dụng GiftGenius chạy ổn (ứng dụng học tập giả định của chúng ta). Cục bộ nó chạy trên Next.js 16 với MCP‑endpoint (ví dụ, /api/mcp) và được xây dựng dựa trên ChatGPT Apps SDK Next.js Starter chính thức.

Bạn có thể đi theo con đường “tôi thuê VPS, tự cài Node, nginx và tự cấu hình mọi thứ”, nhưng với Next.js thì điều đó cũng giống như viết frontend bằng document.write thuần vào năm 2025. Vẫn chạy, nhưng bạn đang tự làm khó mình.

Vercel phù hợp với chúng ta vì vài lý do.

Thứ nhất, nó hiểu Next.js một cách tự nhiên: tự động cấu hình build, SSR, static, lớp edge và các hàm serverless. Với ChatGPT App, điều này đặc biệt tiện lợi vì widget và MCP‑endpoint được triển khai chỉ với một nút và cùng sống trong một hạ tầng.

Thứ hai, Vercel cung cấp CI/CD sẵn sàng: bạn kết nối kho Git — và mỗi lần push sẽ tạo một deployment bất biến mới với URL riêng. Từ nhánh main được coi là production, còn các nhánh khác — preview.

Thứ ba, Vercel có quản lý môi trường và secrets rất tốt. Nó phân tách rõ biến môi trường thành Development, Preview và Production, lưu trữ mã hóa và cho phép truyền chúng vào Next.js một cách tiện lợi. Đây chính xác là điều ChatGPT App cần, nơi key và URL máy chủ MCP phải thay đổi tùy theo môi trường.

Thứ tư, Vercel có rollback tiện lợi: nếu bản phát hành mới có vấn đề, bạn có thể nhanh chóng promote deployment trước đó đã thành công và đưa hệ thống về trạng thái ổn định. Điều này giúp giảm “nỗi sợ deploy” và khuyến khích các bản phát hành nhỏ, thường xuyên.

Và cuối cùng, Vercel là công ty tạo ra Next.js. Họ đã tối ưu Next.js cho máy chủ của họ và ngược lại. Dùng Vercel, bạn sẽ nhiều lần cảm nhận được mọi thứ hoạt động trơn tru chỉ trong vài cú nhấp. Tôi đảm bảo, bạn sẽ thích.

2. Điểm xuất phát: cấu trúc dự án GiftGenius

Theo lộ trình khóa học, GiftGenius của chúng ta sống trong một kho duy nhất. Có hai cách tổ chức và cả hai đều phù hợp với Vercel:

1) Monorepo với nhiều ứng dụng — ví dụ:

giftgenius/
  apps/
    web/   # Next.js (widget + MCP)
    mcp/   # máy chủ MCP riêng (nếu bạn tách ra)

2) Một dự án Next.js duy nhất, nơi cả widget và MCP cùng sống (đơn giản hơn ở giai đoạn đầu và cũng là cách starter chính thức được thiết kế):

giftgenius/
  app/
    page.tsx         # Widget
    api/
      mcp/route.ts   # MCP endpoint
  next.config.mjs
  package.json
  ...

Trong các bài của Module 2 bạn đã clone Apps SDK Starter, cài dependencies và chạy npm run dev. Bây giờ chúng ta giả định rằng:

  • dự án đã ở Git (GitHub / GitLab / Bitbucket);
  • cục bộ bạn dùng .env.local với các key (OPENAI_API_KEY và các key khác);
  • ChatGPT Dev Mode đã nối tới tunnel của bạn.

Mục tiêu của chúng ta — làm sao để cùng đoạn mã đó build và chạy trên Vercel, và ChatGPT có thể gọi tới một tên miền HTTPS ổn định dạng https://giftgenius.vercel.app thay vì tunnel.

3. Chuẩn bị kho để deploy

Trước khi bấm “New Project” trong Vercel, hãy dọn dẹp kho một chút. Các bước này đơn giản nhưng sẽ tiết kiệm rất nhiều thời gian về sau.

Thứ nhất, đảm bảo .env.local.vercel không vào kho. Trong .gitignore của Next.js Starter thường đã có, nhưng tốt nhất vẫn kiểm tra lại:

node_modules
.next
.env.local
.vercel

.env.local — cấu hình và secrets cục bộ của bạn. Không bao giờ đưa nó vào Git, đặc biệt nếu có OPENAI_API_KEY hoặc key cho database. Trên Vercel, chúng ta sẽ lưu secrets riêng trong UI.

Thứ hai, xem package.json. Với Vercel, các scripts chính xác rất quan trọng:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

Mặc định Vercel sẽ gọi npm run build (hoặc pnpm build nếu bạn dùng pnpm). Lệnh này phải build dự án mà không lỗi.

Thứ ba, hãy đảm bảo phiên bản Node được chỉ định và phù hợp với Next.js 16. Theo release notes của Next.js 16, phiên bản tối thiểu là 18.18.0. Thường chỉ cần trường trong package.json:

{
  "engines": {
    "node": ">=18.18.0"
  }
}

Vercel sẽ chọn phiên bản Node LTS tương thích với ứng dụng của bạn.

Nếu mọi thứ đã xong, hãy push mã mới nhất lên Git và chuyển sang Vercel.

4. Nhập dự án lần đầu vào Vercel

Bây giờ chuyển sang giao diện web của Vercel. Nếu bạn chưa đăng ký — đây là lúc thích hợp.

Bạn đăng nhập Vercel, bấm “New Project” và chọn repo giftgenius của mình từ danh sách. Ở bước này, Vercel sẽ kiểm tra nội dung repo và hầu như luôn tự nhận ra đây là dự án Next.js, gán preset tương ứng.

Trong phần cài đặt dự án, Vercel sẽ đề xuất:

  • Framework = Next.js;
  • Build Command = npm run build (hoặc pnpm build/yarn build);
  • Output Directory — mặc định .next (không cần thay đổi).

Cho lần deploy đầu tiên bạn có thể chưa cần khai báo biến môi trường (chúng ta sẽ thêm sau). Bấm “Deploy” — Vercel clone repo, cài dependencies, chạy npm run build và nếu mọi thứ thành công, sẽ tạo deployment đầu tiên với địa chỉ như https://giftgenius-xyz.vercel.app.

Điều quan trọng cần hiểu: mỗi deployment là bất biến. Khi bạn push thay đổi, sẽ tạo một deployment mới với URL mới, còn cái cũ vẫn nằm trong lịch sử. Tên miền production (ví dụ giftgenius.vercel.app hoặc domain tùy chỉnh) trỏ tới một deployment cụ thể và có thể chuyển ngược lại để rollback.

Sơ đồ minh họa như sau:

flowchart LR
    A[Kho GitHub
giftgenius] -->|git push| B[Build trên Vercel] B --> C[Preview Deploy #1
URL duy nhất] B --> D[Preview Deploy #2
URL duy nhất] D --> E[Production alias
giftgenius.vercel.app]

Nhánh Git main thường được coi là nhánh production, phần còn lại — preview. Nhưng bạn có thể cấu hình lại.

5. Biến môi trường trên Vercel

Hiện tại deployment đầu tiên của bạn nhiều khả năng chưa hoạt động tốt: thiếu OPENAI_API_KEY, máy chủ MCP không gọi được API bên ngoài, v.v. Đến lúc cấu hình biến môi trường.

Trên Vercel, biến môi trường nằm ở Settings → Environment Variables. Tại đây bạn cũng sẽ thấy tách thành ba scope: Development, Preview và Production.

Bảng để hình dung:

Scope Nơi dùng Tương ứng local
Development vercel dev và dev cục bộ qua Vercel CLI .env.local
Preview tất cả deployment từ các nhánh không phải nhánh production staging / test
Production deployment từ nhánh production (thường là main) “.production” .env.prod

Khác với .env.local cục bộ ở chỗ Vercel lưu giá trị dưới dạng mã hóa và tự động đưa chúng vào như process.env.MY_VAR trong mã nguồn Next.js.

Rất quan trọng: tiền tố NEXT_PUBLIC_. Mọi biến bắt đầu bằng NEXT_PUBLIC_ sẽ xuất hiện trong bundle trên trình duyệt và ai cũng có thể xem được (mở DevTools là thấy). Điều này tốt cho cấu hình công khai (NEXT_PUBLIC_ENV=preview, NEXT_PUBLIC_API_BASE_URL=https://giftgenius.vercel.app), nhưng hoàn toàn không ổn cho các key như OPENAI_API_KEY.

Với secrets, chúng ta dùng tên không có NEXT_PUBLIC_ và chỉ đọc ở phía server: trong route handlers, công cụ MCP, v.v.

6. Cấu hình env cho GiftGenius: ví dụ

Hãy xem các biến môi trường mà GiftGenius cần tối thiểu.

Tập tối thiểu có thể như sau:

  • OPENAI_API_KEY — khóa để gọi các model / client MCP;
  • APP_BASE_URL — URL gốc của ứng dụng (https://giftgenius.vercel.app hoặc preview‑URL);
  • có thể là GIFTDATA_API_URL hoặc PRODUCTS_API_URL, nếu bạn có danh mục bên ngoài.

Trong phát triển cục bộ, các biến này nằm trong .env.local:

OPENAI_API_KEY=sk-local-...
APP_BASE_URL=http://localhost:3000
PRODUCTS_API_URL=https://dev-api.gifts.example.com

Trên Vercel, bạn vào Settings → Environment Variables và thêm các key/giá trị tương tự, nhưng đặt vào đúng scope tương ứng.

Ví dụ nó trông như thế nào trong mã của MCP‑endpoint:

// app/api/mcp/route.ts
import { NextRequest } from 'next/server';

const apiKey = process.env.OPENAI_API_KEY!; // đừng làm vậy nếu chưa kiểm tra trong code thực tế :)

export async function POST(req: NextRequest) {
  if (!apiKey) {
    return new Response('Missing OPENAI_API_KEY', { status: 500 });
  }
  // Gọi OpenAI hoặc dịch vụ khác với apiKey...
}

Widget có thể dùng APP_BASE_URL ở phía server, ví dụ để xây dựng liên kết tuyệt đối, cân nhắc ChatGPT‑iframe và cấu hình assetPrefix/basePath từ starter.

Nếu cần URL API công khai (ví dụ cho window.fetch tới backend của bạn), bạn có thể tạo NEXT_PUBLIC_API_BASE_URL. Nhưng tuyệt đối không phải NEXT_PUBLIC_OPENAI_API_KEY.

7. Preview‑deploy: staging cực kỳ tiện

Giờ đến phần thú vị nhất: preview‑deploy. Khi bạn đã kết nối repo Git, Vercel tự động tạo preview‑deploy cho mỗi lần push vào nhánh không phải production hoặc cho mỗi Pull Request. Mỗi deployment như vậy có một URL duy nhất, ví dụ:

https://giftgenius-git-feature-new-layout-username.vercel.app

Các deployment này dùng scope Preview cho biến môi trường, vì vậy bạn có thể đặt, chẳng hạn:

# Env Preview trên Vercel
APP_BASE_URL=https://giftgenius-staging.vercel.app
PRODUCTS_API_URL=https://staging-api.gifts.example.com

và không bị lẫn với production.

Từ góc nhìn ChatGPT Dev Mode, preview‑URL là ứng viên lý tưởng cho staging. Trong cài đặt Dev App của bạn, bạn có thể tạm thời đổi endpoint từ URL tunnel sang preview‑URL và quan sát cách phiên bản đã build của GiftGenius hoạt động, trước khi nó trở thành production‑deploy.

Một thói quen phổ biến: cho một tính năng bạn tạo nhánh feature/smart-recommendations, push thay đổi — Vercel cung cấp preview link. Bạn vào Dev Mode, đổi URL sang link này, kiểm thử các kịch bản với GPT (gợi ý quà, hiển thị thẻ, gọi công cụ MCP). Chỉ khi mọi thứ ổn, mới merge vào main. Production vẫn vận hành bình thường.

Sơ đồ pipeline trong đầu:

flowchart TD
    A[Dev cục bộ
localhost + tunnel] --> B[git push
feature/*] B --> C[Preview Deploy
preview-URL] C --> D[ChatGPT Dev Mode
App → preview-URL] C --> E[Code review / tests] E --> F[Merge vào main] F --> G[Production Deploy
prod-URL] G --> H[ChatGPT Prod App
App → prod-URL]

8. Deploy production và rollback

Khi bạn merge thay đổi vào main (hoặc nhánh production bạn chọn), Vercel tạo production‑deploy và gán production‑alias cho nó: giftgenius.vercel.app hoặc domain của bạn.

Tại thời điểm này, ChatGPT Prod App (bạn sẽ tạo sau) nên được cấu hình trỏ tới production‑URL. Ở Dev Mode, bạn tiếp tục thử nghiệm với tunnel hoặc preview‑URL; người dùng bình thường trong ChatGPT Store sẽ truy cập production.

Ưu điểm của các deployment bất biến là rollback rất đơn giản. Nếu bản phát hành mới lỗi (ví dụ, công cụ MCP sập trên dữ liệu thực), bạn không cần chữa cháy ngay trên production. Mở danh sách deployment trong Vercel, chọn deployment thành công trước đó và bấm kiểu “Promote to Production” — K8s và Lambda ở đâu đó sẽ hoán đổi, và domain của bạn lại trỏ về bản ổn định.

Trong CLI cũng có thể tự động hóa với lệnh vercel rollback, nhưng ở mức của khóa học này, hiểu ý tưởng là đủ: mỗi deployment là một artefact riêng, và production‑alias có thể trỏ đến bất kỳ artefact nào trong số đó.

9. Đặc thù Next.js 16 + MCP trên Vercel

Từ góc độ Vercel, MCP‑endpoint trong Next.js của bạn là một hàm serverless (hoặc edge function, nếu bạn cấu hình vậy). Nó sống ngắn: “thức dậy” khi có request, xử lý xong là chết. Không thể lưu trạng thái giữa các lần gọi, trừ khi bạn dùng DB hoặc kho lưu trữ bên ngoài.

Điều này rất quan trọng với MCP: nếu bạn định lưu lịch sử hội thoại vào mảng toàn cục let history = [] trong route.ts, nó sẽ bị reset ở mỗi lần cold start. Để lưu trạng thái, bạn cần hệ thống bên ngoài (KV, Postgres, v.v.), nhưng đó là nội dung cho các module sau.

Khía cạnh thứ hai — timeout thực thi. Trên gói miễn phí, hàm serverless của Vercel bị giới hạn thời gian (tại thời điểm soạn tài liệu — khoảng 10 giây trên Hobby, nhiều hơn trên Pro). Với các request LLM và đặc biệt chuỗi công cụ MCP, từng đó có thể là ít.

Trong Next.js 16, với route handlers bạn có thể đặt maxDuration để yêu cầu Vercel tăng thời gian (trong giới hạn gói):

// app/api/mcp/route.ts
export const maxDuration = 60; // số giây; trên Pro có thể tới 300

export async function POST(req: Request) {
  // tác vụ dài: gọi OpenAI, DB bên ngoài, v.v.
}

Đây không phải là “nút phép thuật làm bao lâu cũng được”, nhưng là cách đúng để nói với Vercel: “hàm này có thể chạy lâu hơn, xin đừng dừng quá sớm”.

Cuối cùng, đừng quên các đặc thù của ChatGPT‑iframe. Trong Apps SDK Starter đã cấu hình sẵn assetPrefixbasePath để static và routes hoạt động đúng bên trong các iframe lồng nhau của web-sandbox.oaiusercontent.com. Nhờ đó, mọi request đều đi tới domain của bạn, không phải sandbox. Khi deploy lên Vercel, cấu hình này vẫn giữ nguyên, nên bạn có widget hoạt động đúng ngay từ đầu.

10. Tích hợp với ChatGPT sau khi deploy

Mặc dù phần này gần với các module về Store/production hơn, vòng đời ứng dụng và logic tích hợp với ChatGPT sau khi deploy khá đơn giản và phù hợp để nói ngay bây giờ.

Đầu tiên bạn deploy GiftGenius lên Vercel và nhận production‑URL. Sau đó trong ChatGPT, ở Dev Mode, tạo một App riêng, ví dụ GiftGenius Prod, và trong cài đặt của nó, chỉ định endpoint là URL này (chính xác là MCP‑endpoint như https://giftgenius.vercel.app/api/mcp theo hướng dẫn OpenAI Apps SDK Deploy).

Cho phát triển, bạn tiếp tục dùng Dev App trỏ tới tunnel hoặc preview‑URL. Để kiểm thử các bản build ngày/tuần, bạn có thể tạo Staging App, liên kết nó với preview alias cố định. Kết quả là sơ đồ ba tầng:

Dev App     → tunnel cục bộ hoặc dev-URL (không ổn định)
Staging App → preview/staging URL ổn định trên Vercel
Prod App    → production URL trên Vercel

Để tham chiếu nhanh, gộp tất cả vào một bảng:

Mục URL / deploy trên Vercel Scope trên Vercel Ai sử dụng
Dev App tunnel cục bộ / vercel dev Development bạn / đội ngũ
Staging App preview alias ổn định Preview đội ngũ / QA
Prod App giftgenius.vercel.app / tên miền tùy chỉnh Production người dùng

Đây chính là mô hình local / staging / prod đã nói ở đầu module, chỉ là giờ gắn với Vercel và ChatGPT Apps. Đây là kiến trúc của một dự án trưởng thành, không còn localhost mãi mãi.

11. Các lỗi thường gặp khi deploy lên Vercel

Lỗi №1: secrets chỉ nằm trong .env.local, còn trên Vercel thì không.
Rất hay gặp: cục bộ mọi thứ chạy, bạn tự tin bấm “Deploy”, ứng dụng build xong, nhưng công cụ MCP trên production trả về 500 với nội dung “Missing OPENAI_API_KEY”. Lý do đơn giản: Vercel không biết về .env.local cục bộ của bạn. Cần nhập riêng các biến đó vào cài đặt dự án trên Vercel (và đúng scope: Preview, Production).

Lỗi №2: dùng NEXT_PUBLIC_ cho dữ liệu nhạy cảm.
Mong muốn “miễn là chạy” đôi khi lấn át, và lập trình viên tạo NEXT_PUBLIC_OPENAI_API_KEY để truy cập key từ client. Kết quả là key nằm trong JS bundle và ai cũng xem được. Đây không chỉ là thực hành tệ, mà còn là con đường thẳng tới rò rỉ và bị khóa key. Tất cả secrets — chỉ không có tiền tố và chỉ dùng ở phía server.

Lỗi №3: môi trường không đồng bộ giữa local và Vercel.
Cục bộ bạn có thể dùng một URL cho sản phẩm (http://localhost:4000), trên Vercel — URL khác (https://api.gifts-staging.com), và trên production — URL thứ ba. Nếu không giữ danh sách biến môi trường cẩn thận và không kiểm tra Preview/Production đã điền đúng, rất dễ gặp cảnh widget production gọi vào backend staging, còn widget staging lại gọi vào production. Kỷ luật đơn giản sẽ giúp: ghi chép đầy đủ các biến cần thiết và kiểm tra chúng trên từng môi trường.

Lỗi №4: bỏ qua giới hạn thời gian thực thi cho các MCP‑endpoint.
Trên máy cục bộ, bạn có thể chờ một hệ thống bên ngoài chậm chạp trong 30 giây mà không thấy vấn đề. Trên Vercel, cùng hàm đó sẽ bị timeout sau 10–15 giây, và ChatGPT sẽ thấy lỗi. Nếu bạn không cấu hình maxDuration và không theo dõi thời gian chạy của công cụ MCP, trên production sẽ xuất hiện các lỗi rơi rụng ngẫu nhiên.

Lỗi №5: cố lưu trạng thái MCP trong bộ nhớ của hàm serverless.
Đôi khi rất muốn đặt lịch sử hội thoại hoặc cache gợi ý vào biến toàn cục let cache = {} ngay trong tệp route handler. Trên local, khi dev server chạy lâu, điều đó thậm chí có thể “hoạt động”. Nhưng trên Vercel, mỗi hàm serverless sống ngắn và thường được tạo lại. Kết quả: một số request “thấy” cache cũ, một số — cache mới, và một số — trống. Điều này gây ra các bug lạ khó tái hiện. Để có trạng thái, cần DB hoặc KV bên ngoài; ở mức bài giảng này, tốt nhất hãy coi MCP‑endpoint là stateless.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION