تطوير الويب

نشر TanStack Start على Cloudflare Workers: KV وD1 وR2 خطوة بخطوة 2026

5 دقائق للقراءة

السلسلة: هذا الدرس جزء من سلسلة 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، روّج نسخة سابقة. فوري.

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité