Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
4 changes: 2 additions & 2 deletions docs/DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

- Node.js **22+** for Wrangler 4 (`wrangler` refuses Node 20). Next.js itself runs on Node 20.9+.
- A Cloudflare account and an API token (see [Token permissions](#token-permissions)).
- `npm install`. It runs `postinstall` → `scripts/patch-opennext.mjs`, which patches `@opennextjs/cloudflare` so it also loads Next 16.4's `preview-props.json`. The patch is idempotent and safe to re-run.
- `npm install`. It runs `postinstall` → `scripts/patch-opennext.mjs`, which patches `@opennextjs/cloudflare` so it also loads Next 16.4's `preview-props.json`. The patch is idempotent and safe to re-run. `npm run cf:build` (also used by `preview` and `deploy`) runs `scripts/check-opennext-patch.mjs` before and after the build and exits 1 if the patch is missing.

### 2. Local development

Expand Down Expand Up @@ -93,7 +93,7 @@ npm run db:migrations:list
npm run db:migrate:remote

# 3) build + deploy this Worker and its configured custom domains
npm run deploy # = opennextjs-cloudflare build && opennextjs-cloudflare deploy
npm run deploy # = npm run cf:build (with patch checks) && opennextjs-cloudflare deploy

# 4) secret (after the worker exists)
printenv OPENAI_API_KEY | npx wrangler secret put OPENAI_API_KEY
Expand Down
1 change: 1 addition & 0 deletions docs/architecture/cloudflare-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
| [`.dev.vars.example`](../../.dev.vars.example) | ต้นแบบ `.dev.vars` สำหรับ `npm run preview` (ห้าม commit `.dev.vars`) |
| [`public/_headers`](../../public/_headers) | headers ของ static assets ที่เสิร์ฟตรงจาก `.open-next/assets` |
| [`scripts/patch-opennext.mjs`](../../scripts/patch-opennext.mjs) | patch ตอน `postinstall` ให้ adapter inline `preview-props.json` ของ Next 16.4 (ไม่งั้นทุกคำขอ error `Unexpected loadManifest(...preview-props.json)`) idempotent และเตือนถ้า pattern เปลี่ยน ลบได้เมื่อ adapter แก้เอง |
| [`scripts/check-opennext-patch.mjs`](../../scripts/check-opennext-patch.mjs) | รันใน `npm run cf:build` (และ `preview`/`deploy`) ให้ build ล้มด้วย exit 1 ถ้า patch ข้างบนไม่ได้ติดตั้ง หรือ `.open-next` ไม่ได้ inline `preview-props.json` (เคยทำให้ version `0286ef58` ได้ 500 ทุกคำขอ) |
| [`next.config.ts`](../../next.config.ts) | headers, 404 rewrite, และ `initOpenNextCloudflareForDev()` ใน dev เพื่อให้ `next dev` เห็น binding |

## การทำงานตอนรัน
Expand Down
5 changes: 3 additions & 2 deletions docs/operations/local-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ cp .env.example .env.local # สำหรับ next dev (ถ้าต้
cp .dev.vars.example .dev.vars # สำหรับ wrangler preview (ถ้าต้องการ AI)
```

`postinstall` patch adapter ของ OpenNext ให้รองรับ `preview-props.json` ของ Next 16.4 ถ้าเห็น `[patch-opennext] pattern not found` แปลว่า adapter เปลี่ยนเวอร์ชันแล้ว ให้ตรวจว่ายังต้อง patch หรือไม่ (ดู [cloudflare-runtime.md](../architecture/cloudflare-runtime.md))
`postinstall` patch adapter ของ OpenNext ให้รองรับ `preview-props.json` ของ Next 16.4 ถ้าเห็น `[patch-opennext] pattern not found` แปลว่า adapter เปลี่ยนเวอร์ชันแล้ว ให้ตรวจว่ายังต้อง patch หรือไม่ (ดู [cloudflare-runtime.md](../architecture/cloudflare-runtime.md)) `npm run cf:build` ตรวจ patch นี้ทั้งก่อนและหลัง build ด้วย `scripts/check-opennext-patch.mjs` ถ้าขึ้น `[check-opennext-patch] ERROR` แปลว่าติดตั้งแบบข้าม postinstall (เช่น `--ignore-scripts`) ให้รัน `npm ci` ใหม่

## สองวิธีรัน และควรใช้เมื่อไร

Expand All @@ -26,7 +26,7 @@ cp .dev.vars.example .dev.vars # สำหรับ wrangler preview (ถ้

ข้อสังเกตจากประสบการณ์: `next dev` บางครั้งแสดง CSS ค้าง (stale) หลังแก้ token มาก ๆ ให้ยืนยันภาพด้วย `npm run preview` ก่อนสรุปผลเสมอ

`npm run preview` = `opennextjs-cloudflare build && opennextjs-cloudflare preview` build ใช้เวลาประมาณ 1 นาที ถ้าต้องการรัน preview ซ้ำโดยไม่ build ใหม่: `npx opennextjs-cloudflare preview --port 8787` ปิด preview ที่ค้าง: `pkill -f "opennextjs-cloudflare preview"; pkill -f workerd`
`npm run preview` = `npm run cf:build && opennextjs-cloudflare preview` build ใช้เวลาประมาณ 1 นาที ถ้าต้องการรัน preview ซ้ำโดยไม่ build ใหม่: `npx opennextjs-cloudflare preview --port 8787` ปิด preview ที่ค้าง: `pkill -f "opennextjs-cloudflare preview"; pkill -f workerd`

## ชุดตรวจก่อน commit

Expand All @@ -49,6 +49,7 @@ npm run cf:build # ต้องผ่านก่อน deploy
## ปัญหาที่พบบ่อย

- **`wrangler` บอกว่า Node เก่าเกินไป** → ใช้ Node 22+
- **หลัง `opennextjs-cloudflare build` แล้วรัน `wrangler dev` ตรงๆ ทุกหน้า (เช่น `/login`) ค้างแล้วได้ 500** (log: `Worker's code had hung`) → ยังไม่ได้คัดลอก `.open-next/cache` ไปที่ `.open-next/assets/cdn-cgi/_next_cache` ให้รัน `npm run preview:local` (หรือ `npx opennextjs-cloudflare populateCache local` ก่อน `wrangler dev`) `opennextjs-cloudflare preview` และ `deploy` ทำขั้นนี้ให้อยู่แล้ว
- **หน้า `/builder` ค้างที่ "Loading builder…"** → เปิด `http://localhost:3000` Next.js 16 บล็อก HMR เมื่อต้นทางเป็น `127.0.0.1` (ตอบคำว่า `Unauthorized` แบบไม่มีสถานะ HTTP) หน้า client-only จึงไม่ hydrate
- **พอร์ต 8787 ถูกใช้** → ปิด preview เก่าตามคำสั่งข้างบน
- **AI ไม่ทำงานใน dev** → ตรวจว่า `OPENAI_API_KEY` อยู่ใน `.env.local` หรือ `.dev.vars` และตรงรูปแบบ `^[\w-]{20,400}$`; `GET /api/plan` จะบอก `serverKeyConfigured`
Expand Down
6 changes: 4 additions & 2 deletions docs/reference/npm-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
| `npm run test:watch` | `vitest` แบบ watch | ระหว่างเขียน test |
| `npm run typecheck` | `tsc --noEmit` | ก่อน commit; CI |
| `npm run export -- <id> [opts]` | `tsx scripts/export-template.ts` เขียน setup.sh/ps1/README ของเทมเพลตลงดิสก์ | ทดสอบเทมเพลต end-to-end |
| `npm run cf:build` | `opennextjs-cloudflare build` → `.open-next/` | ตรวจว่า build เป็น Worker ได้ |
| `npm run cf:build` | `scripts/check-opennext-patch.mjs` → `opennextjs-cloudflare build` → `.open-next/` → ตรวจ output อีกรอบ | ตรวจว่า build เป็น Worker ได้ ถ้า patch ของ OpenNext ไม่ได้ติดตั้ง จะหยุดด้วย exit 1 |
| `npm run preview` | build + `opennextjs-cloudflare preview` ใน workerd ที่ :8787 | ตรวจพฤติกรรม/ภาพแบบ production |
| `npm run deploy` | build + `opennextjs-cloudflare deploy` | **ต้องได้รับอนุมัติก่อน** ดู [deploy-cloudflare.md](../operations/deploy-cloudflare.md) |
| `npm run preview:local` | `opennextjs-cloudflare populateCache local` แล้ว `wrangler dev` (ใช้หลัง `npm run cf:build`) | รัน build ที่มีอยู่ใน workerd ในเครื่อง ถ้ารัน `wrangler dev` ตรงๆ โดยไม่ populate cache ทุกหน้า App Router จะค้างแล้วได้ 500 |
| `npm run deploy` | `npm run cf:build` + `opennextjs-cloudflare deploy` | **ต้องได้รับอนุมัติก่อน** ดู [deploy-cloudflare.md](../operations/deploy-cloudflare.md) |
| `npm run cf:typegen` | `wrangler types ...` → `cloudflare-env.d.ts` | หลังแก้ binding ใน wrangler.jsonc |
| `npm run db:migrate:local` | `wrangler d1 migrations apply nvx-db --local` | ทดสอบ migration ในฐานข้อมูลจำลองบนเครื่อง |
| `npm run db:migrate:remote` | `wrangler d1 migrations apply nvx-db --remote` | **ต้องได้รับอนุมัติก่อน** apply บน D1 จริง ดู [d1-database.md](../operations/d1-database.md) |
Expand Down Expand Up @@ -50,6 +51,7 @@ npm run export -- fastapi-ai --py uv --addons testing,docker --out /tmp/fastapi
|---|---|---|
| [`scripts/gen-brand-icons.mjs`](../../scripts/gen-brand-icons.mjs) | `node scripts/gen-brand-icons.mjs` | สร้าง `src/components/brand/brand-icons.data.ts` จาก simple-icons (รันหลังเพิ่ม id โลโก้) |
| [`scripts/patch-opennext.mjs`](../../scripts/patch-opennext.mjs) | อัตโนมัติ (postinstall) | patch `preview-props.json` |
| [`scripts/check-opennext-patch.mjs`](../../scripts/check-opennext-patch.mjs) | อัตโนมัติ (`cf:build`) | exit 1 ถ้า adapter ไม่ได้ patch (ก่อน build) หรือ `.open-next` ไม่ได้ inline `preview-props.json` (หลัง build) |
| [`scripts/docs-check.mjs`](../../scripts/docs-check.mjs) | `npm run docs:check` | docs freshness |
| [`scripts/docs-pdf.mjs`](../../scripts/docs-pdf.mjs) | `npm run docs:pdf` | PDF คู่มือผู้ใช้ |
| [`scripts/build-docs.mjs`](../../scripts/build-docs.mjs) | `npm run docs:bundle` (และ pre-hooks) | docs bundle สำหรับ `/docs` ([docs-site.md](../architecture/docs-site.md)) |
Expand Down
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
"pretypecheck": "node scripts/build-docs.mjs && next typegen",
"typecheck": "tsc --noEmit",
"export": "tsx scripts/export-template.ts",
"cf:build": "opennextjs-cloudflare build",
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"cf:build": "node scripts/check-opennext-patch.mjs && opennextjs-cloudflare build && node scripts/check-opennext-patch.mjs --output",
"preview": "npm run cf:build && opennextjs-cloudflare preview",
"preview:local": "opennextjs-cloudflare populateCache local && wrangler dev",
"deploy": "npm run cf:build && opennextjs-cloudflare deploy",
"cf:typegen": "wrangler types --env-interface CloudflareEnv --include-runtime=false cloudflare-env.d.ts",
"db:migrate:local": "wrangler d1 migrations apply nvx-db --local",
"db:migrate:remote": "wrangler d1 migrations apply nvx-db --remote",
Expand Down Expand Up @@ -92,7 +93,9 @@
"vitest": "^4.1.11",
"wrangler": "^4.149.0"
},
"engines": {},
"engines": {
"node": ">=22"
},
"overrides": {
"@node-minify/core": {
"glob": "10.5.0"
Expand Down
37 changes: 37 additions & 0 deletions scripts/check-opennext-patch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Fails the Cloudflare build when the OpenNext compatibility patch is missing.
// scripts/patch-opennext.mjs (postinstall) adds preview-props.json to the manifests the adapter
// inlines. If postinstall was skipped (npm ci --ignore-scripts, pnpm/bun without lifecycle scripts,
// or a reinstall of the adapter), the Worker builds fine but every request fails in production with
// "Unexpected loadManifest(/.next/server/preview-props.json) call!" (Worker version 0286ef58, 2026-10-17).
//
// node scripts/check-opennext-patch.mjs check node_modules before `opennextjs-cloudflare build`
// node scripts/check-opennext-patch.mjs --output check .open-next after the build
//
// Keep the pattern in sync with scripts/patch-opennext.mjs.
import { existsSync, readFileSync } from "node:fs";

const ADAPTER_FILE = "node_modules/@opennextjs/cloudflare/dist/cli/build/patches/plugins/load-manifest.js";
const PATCHED = "**/{*-manifest,required-server-files,prefetch-hints,preview-props}.json";
const HANDLER = ".open-next/server-functions/default/handler.mjs";
// The inlined loadManifest() returns the manifest object for preview-props.json when the patch was active.
const INLINED = /endsWith\(\s*["'`]\/server\/preview-props\.json["'`]\s*\)\s*\)\s*\{?\s*return\s*\{/;
const FIX = "Run `npm ci` (with install scripts enabled) or `node scripts/patch-opennext.mjs`, then build again.";

function fail(msg) {
console.error(`\n[check-opennext-patch] ERROR: ${msg}\n[check-opennext-patch] ${FIX}\n`);
process.exit(1);
}

if (process.argv.includes("--output")) {
if (!existsSync(HANDLER)) fail(`${HANDLER} not found; the OpenNext build did not produce a server handler.`);
if (!INLINED.test(readFileSync(HANDLER, "utf8"))) {
fail(`${HANDLER} does not inline .next/server/preview-props.json. Every request would fail with "Unexpected loadManifest(/.next/server/preview-props.json) call!". Do not deploy this build.`);
}
console.log("[check-opennext-patch] build output inlines preview-props.json");
} else {
if (!existsSync(ADAPTER_FILE)) fail(`${ADAPTER_FILE} not found; @opennextjs/cloudflare is not installed or its layout changed.`);
if (!readFileSync(ADAPTER_FILE, "utf8").includes(PATCHED)) {
fail(`@opennextjs/cloudflare is not patched (scripts/patch-opennext.mjs did not run): ${ADAPTER_FILE} does not contain the preview-props.json pattern.`);
}
console.log("[check-opennext-patch] adapter patch present");
}
Loading