السلسلة: هذا الدرس جزء من سلسلة TanStack. اقرأ المقال الرئيسي.
Cloudflare Workers صار في 2026 الخيار الافتراضي لاستضافة تطبيق TanStack Start حديث بلا دفع VPS. PoP محلي يخدم HTML في أقل من 100 مللي ثانية لزائر MENA، حصة مجانية تغطي 100 000 طلب يومياً، وbindings KV وD1 وR2 متاحة مباشرة من server functions بلا client خارجي.
المتطلبات
- حساب Cloudflare (tier مجاني يكفي)
- Node.js 22 LTS وnpm 10+
- Wrangler CLI:
npm install -g wranglerثمwrangler login - معرفة أساسية بـ SSR وserver functions TanStack Start
- 45 دقيقة
الخطوة 1 — إنشاء المشروع بـ template Cloudflare الرسمي
npm create cloudflare@latest -- my-tanstack-app --framework=tanstack-start
cd my-tanstack-app
npm run dev
الأداة تطرح سؤالين، اقبل افتراضياً TypeScript وGit. عند npm run dev، URL http://localhost:5173 يجب أن يخدم صفحة استقبال مرئية SSR.
الخطوة 2 — قراءة vite.config.ts المولَّد
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
cloudflare({ viteEnvironment: { name: 'ssr' } }),
tanstackStart(),
react(),
],
})
الخيار viteEnvironment: { name: 'ssr' } يخبر الملحق Cloudflare باستهداف بيئة SSR لـ TanStack Start. الترتيب مهمّ: cloudflare أولاً، وإلا تحويلات TanStack Start غير متوافقة مع runtime Workers.
الخطوة 3 — ضبط wrangler.jsonc
// wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-tanstack-app",
"compatibility_date": "2026-05-06",
"compatibility_flags": ["nodejs_compat"],
"main": "@tanstack/react-start/server-entry",
"observability": { "enabled": true }
}
compatibility_date يقفل runtime Workers على التاريخ. compatibility_flags: ["nodejs_compat"] يفعّل توافق Node.js، لا غنى عنه لحزم تستخدم node:buffer أو node:crypto. main يشير إلى مدخل خادم TanStack Start.
الخطوة 4 — إنشاء binding KV لـ cache auth
wrangler kv namespace create "SESSION_CACHE"
الأمر يطبع ID namespace بشكل UUID. أضف القسم kv_namespaces إلى wrangler.jsonc:
{
"name": "my-tanstack-app",
"compatibility_date": "2026-05-06",
"compatibility_flags": ["nodejs_compat"],
"main": "@tanstack/react-start/server-entry",
"kv_namespaces": [
{
"binding": "SESSION_CACHE",
"id": "votre-id-uuid-ici"
}
]
}
الخطوة 5 — قراءة binding في server function
Bindings غير متاحة عبر process.env على Workers — تعيش في كائن env محدّد. TanStack Start يكشف env عبر الموديول الافتراضي cloudflare:workers.
// app/server/session.ts
import { createServerFn } from '@tanstack/react-start'
import { env } from 'cloudflare:workers'
export const getSession = createServerFn({ method: 'GET' }).handler(async () => {
const value = await env.SESSION_CACHE.get('current-user')
return value ? JSON.parse(value) : null
})
export const setSession = createServerFn({ method: 'POST' }).handler(
async ({ data }: { data: { user: string } }) => {
await env.SESSION_CACHE.put(
'current-user',
JSON.stringify(data),
{ expirationTtl: 60 * 60 * 24 }, // 24 ساعة
)
return { ok: true }
},
)
في local عبر npm run dev، Wrangler يحاكي binding KV عبر Miniflare، فيمكن الاختبار بلا نشر.
الخطوة 6 — توصيل قاعدة D1 للـ persistance
D1 قاعدة SQLite موزَّعة من Cloudflare. transactions ACID، SQL معياري، كمون منخفض.
wrangler d1 create my-tanstack-db
# يطبع database_id للنسخ
// wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "my-tanstack-db",
"database_id": "votre-database-id"
}
]
}
echo "CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT, body TEXT);" > migrations/0001_init.sql
wrangler d1 execute my-tanstack-db --local --file=migrations/0001_init.sql
wrangler d1 execute my-tanstack-db --remote --file=migrations/0001_init.sql
--local يعمل على قاعدة Miniflare المحلية، --remote على الإنتاج. الاثنان يجب أن يبقيا متزامنين.
الخطوة 7 — قراءة وكتابة في D1 من server function
// app/server/posts.ts
import { createServerFn } from '@tanstack/react-start'
import { env } from 'cloudflare:workers'
export const getPosts = createServerFn({ method: 'GET' }).handler(async () => {
const { results } = await env.DB
.prepare('SELECT id, title, body FROM posts ORDER BY id DESC')
.all<{ id: number; title: string; body: string }>()
return results
})
export const createPost = createServerFn({ method: 'POST' }).handler(
async ({ data }: { data: { title: string; body: string } }) => {
const result = await env.DB
.prepare('INSERT INTO posts (title, body) VALUES (?, ?) RETURNING id')
.bind(data.title, data.body)
.first<{ id: number }>()
return { id: result?.id }
},
)
prepare(...).bind(...).all() هي النسخة الآمنة — مرّر دائماً عبر bind لتجنّب SQL injection.
الخطوة 8 — تخزين ملفات في bucket R2
R2 خدمة Cloudflare متوافقة S3، بلا رسوم عرض نطاق صادر.
wrangler r2 bucket create avatars-bucket
// wrangler.jsonc
{
"r2_buckets": [
{
"binding": "AVATARS",
"bucket_name": "avatars-bucket"
}
]
}
// app/server/upload.ts
import { createServerFn } from '@tanstack/react-start'
import { env } from 'cloudflare:workers'
export const uploadAvatar = createServerFn({ method: 'POST' })
.inputValidator((input: unknown) => {
if (!(input instanceof FormData)) throw new Error('FormData attendu')
const file = input.get('file')
if (!(file instanceof File)) throw new Error('Champ file manquant')
return { file }
})
.handler(async ({ data }) => {
const key = 'avatars/' + crypto.randomUUID() + '.' + data.file.type.split('/')[1]
await env.AVATARS.put(key, data.file.stream(), {
httpMetadata: { contentType: data.file.type },
})
return { key }
})
الخطوة 9 — النشر إنتاجياً
npm run build
npm run deploy
deploy يطبع URL https://my-tanstack-app.<حسابك>.workers.dev. لنطاق مخصّص، اذهب إلى dashboard Cloudflare واربطه بهذا Worker عبر قسم Routes.
الخطوة 10 — التحقق من السجلات والمقاييس
wrangler tail my-tanstack-app --format pretty
الأمر يعرض آنياً كل طلب HTTP مع رمز الإرجاع وconsole.log. أفضل أداة تشخيص لفهم لماذا server function ترفع في الإنتاج.
اختلافات runtime يجب معرفتها
| الجانب | Node.js | Workers |
|---|---|---|
| API ملف | fs متاح |
لا نظام ملفات، استخدم R2 |
| Modules أصلية | better-sqlite3 |
غير مدعومة، استخدم D1 |
process.env |
معياري | متغيّرات عبر env.* من binding |
| حدّ CPU | لا شيء | 50 مللي مجاناً، 30 ث مدفوع |
| حدّ ذاكرة | من الخادم | 128 ميغا لكل invocation |
| Cold start | متغيّر | شبه صفر (V8 isolates) |
| WebSocket | معياري | عبر Durable Objects |
أخطاء شائعة
| الخطأ | السبب | الحل |
|---|---|---|
Cannot resolve "node:fs" |
تبعية Node-only في الحزمة | فعّل nodejs_compat أو ابحث عن بديل |
env.MY_KV is undefined |
binding غير مُعلَن في wrangler.jsonc | تحقّق kv_namespaces وID |
| Build OK لكن 404 في كل مكان | main سيء الإعداد |
أشِر إلى @tanstack/react-start/server-entry |
| HTML ثابت فقط | ملحق Cloudflare مفقود | أضف cloudflare() في vite.config.ts |
| migrations D1 ناقصة في prod | نسيان --remote |
أعد wrangler d1 execute --remote |
| حدّ CPU متجاوَز | حساب ثقيل في handler | فوّض إلى Durable Object أو خدمة خارجية |
أسئلة شائعة
هل tier مجاني يكفي للبدء؟ نعم، لـ 100 000 طلب يومياً، 1 جيغا KV، 5 جيغا D1 و10 جيغا R2. ما بعد، خطة Workers Paid بنحو 5 USD/شهر.
هل يمكن ربط قاعدة PostgreSQL خارجية (Neon، Supabase)؟ نعم، عبر Hyperdrive (proxy Cloudflare) أو TCP عبر Connect API. D1 يبقى أبسط وأرخص لمعظم الحالات.
كيف ندير متغيّرات بيئة سرية؟ wrangler secret put MY_SECRET يخزّن السرّ جانب Cloudflare، متاح عبر env.MY_SECRET. لا تُودِع أسراراً أبداً في wrangler.jsonc.
هل build طويل؟ على مشروع TanStack Start متوسط، npm run build يدور 15-30 ثانية، وwrangler deploy 5-10 ثوان إضافية. الانتشار العالمي فوري.
كيف نتراجع عند مشكلة؟ Cloudflare يحفظ الإصدارات السابقة. dashboard Workers ← Deployments، روّج نسخة سابقة. فوري.