سرور رینڈرنگ اور جزوی پری رینڈرنگ
runtime = "server": سرور پر رینڈر شدہ روٹس کی فریمز میں اسٹریمنگ؛ پری رینڈر شدہ کونسول شیلز۔
روٹ کے React ٹری کو سرور پر رینڈر کریں اور اس کے فریم کونسول میں اسٹریم کریں۔ ہر اسٹیٹک روٹ کے پہلے فریم کی پری رینڈرنگ اس کے HTML میں کریں۔
دو رن ٹائم
ہر صفحہ دو میں سے ایک جگہ رینڈر ہوتا ہے:
| کلائنٹ (ڈیفالٹ) | سرور (runtime = "server") | |
|---|---|---|
| React ٹری چلنے کی جگہ | براؤزر ٹیب | text dev / text start / bun اڈاپٹر (Bun یا Node) |
| براؤزر کا ڈاؤن لوڈ | پوری ایپ: React، Yoga WASM، آپ کے روٹس | فریم پلیئر، تقریباً 21 kB gzip |
| ڈیٹا تک رسائی | روٹ ہینڈلرز کو fetch() | براہ راست: ڈیٹابیس، فائلیں، node:*، سیکرٹس |
| ایسِنک کمپوننٹس | use() یا ایفیکٹس استعمال کریں | async function کمپوننٹس کی سپورٹ موجود ہے |
| کلید دبانے کی تاخیر | اسی ٹاسک میں، سنکرونس | سرور تک ایک راؤنڈ ٹرپ |
| اسٹیٹک ہوسٹ پر کام | ہاں | نہیں، اسے text start یا bun dist/server.js درکار ہے |
دونوں رن ٹائم ایک جیسے فریم بناتے ہیں۔ فریم پلیئر اسی CssConsoleDriver سے سیل انکوڈ کرتا ہے اور اسی ~15fps کی رفتار پر چلتا ہے، اس لیے سرور پر رینڈر شدہ فریم کلائنٹ کی console.log کال سے بائٹ بہ بائٹ ملتا ہے۔
فعال کرنا
صفحے سے runtime ایکسپورٹ کریں:
import { Suspense, type ReactNode } from "react";import os from "node:os";import { Box, Text } from "@textjs/app";export const runtime = "server";async function Load(): Promise<ReactNode> { const [one, five] = os.loadavg(); return <Text>{`load ${one.toFixed(2)} ${five.toFixed(2)}`}</Text>;}export default function StatusPage(): ReactNode { return ( <Box flexDirection="column"> <Text bold>Status</Text> <Suspense fallback={<Text color="gray">Reading host…</Text>}> <Load /> </Suspense> </Box> );}ہر صفحے کا runtime ایک اسٹیٹک اسٹرنگ ایکسپورٹ کے طور پر پڑھا جاتا ہے (ٹائپ اینوٹیشن، as const یا دوبارہ ایکسپورٹ درست ہیں؛ حساب سے نکالی گئی قدر درست نہیں)۔ لے آؤٹس، loading.tsx، error.tsx اور not-found.tsx وہیں رینڈر ہوتے ہیں جہاں ان کا صفحہ رینڈر ہوتا ہے: کلائنٹ اور سرور صفحات کا مشترک لے آؤٹ دونوں جگہ چلتا ہے اور براؤزر کو بھیجا جاتا ہے، جبکہ صرف سرور صفحات والے سیگمنٹ کے لے آؤٹس اور باؤنڈریز سرور پر رہتے ہیں۔ ہکس، فوکس، useInput، <Link> اور useRouter() کا رویہ کلائنٹ جیسا ہی ہوتا ہے۔
کس وقت کون سا رن ٹائم
ان کے لیے کلائنٹ رن ٹائم (ڈیفالٹ) استعمال کریں:
- زیادہ ٹائپنگ والے UI جہاں ہر کلید دبانے پر اسی ٹاسک میں پینٹ ہونا ضروری ہو، جیسے ایڈیٹرز اور فلٹرز۔
- اسٹیٹک ہوسٹ یا CDN پر ڈیپلائے ہونے والی ایپس۔
- آف لائن کام کرنے والے ٹولز۔
ان کے لیے سرور رن ٹائم استعمال کریں:
- ڈیٹابیس، فائلیں، قطاریں یا نجی APIs پڑھنے والے ڈیش بورڈز اور ایڈمن ویوز۔ اسناد سرور پر رہتی ہیں۔
- بھاری ڈیپینڈنسیز (SDKs، پارسرز، سنٹیکس ٹیبلز) والے روٹس جنہیں آپ براؤزر بنڈل میں نہیں رکھنا چاہتے۔
- سست نیٹ ورکس پر تیزی سے لوڈ ہونے والے صفحات۔ براؤزر پوری ایپ کے بجائے 21 kB (gzip) کا پلیئر ڈاؤن لوڈ کرتا ہے۔
آپ ایک ایپ میں مختلف رن ٹائم ملا سکتے ہیں۔ ان کے درمیان نیویگیشن ہدف URL کو نئی دستاویز کے طور پر لوڈ کرتی ہے۔ براؤزر بنڈل ہر سرور صفحے کو ایک اسٹب سے بدل دیتا ہے، اس لیے سرور ماڈیولز اور ان کی امپورٹ کردہ کوئی بھی چیز کلائنٹ کو نہیں بھیجی جاتی۔
صرف سرور کے ماڈیولز کو *.server.ts نام دیں۔ اگر براؤزر کو بھیجا جانے والا کوڈ (کلائنٹ صفحہ، یا اس کا استعمال کردہ لے آؤٹ) کسی *.server.* ماڈیول یا node:* بلٹ اِن کو امپورٹ کرے تو بلڈ ناکام ہوتا ہے اور امپورٹ کرنے والے کا نام بتاتا ہے۔
مڈل ویئر سرور روٹس کی حفاظت دستاویزات کی طرح کرتا ہے: یہ ہدف روٹ کے لیے سیشن کھلنے سے پہلے، سیشن کے اندر ہر نیویگیشن (router.push، واپس اور آگے) سے پہلے، اور پھر جب بھی سیشن کی کوکیز یا Authorization بدلیں، چلتا ہے۔ جب اسے ری ڈائریکٹ، مسترد یا ری رائٹ کرنا ہو، تو براؤزر اس کے بجائے URL کو دستاویز کے طور پر لوڈ کرتا ہے، اور مڈل ویئر اس درخواست کا جواب دیتا ہے۔
ایسِنک سرور کمپوننٹس
سرور پر، ہر async function کمپوننٹ اپنے پرامس کے حل ہونے تک قریب ترین <Suspense> یا loading.tsx پر معطل رہتا ہے:
export const runtime = "server";async function Deployments({ team }: { team: string }) { const rows = await db.deployments.list({ team }); return <Table data={rows} columns={COLUMNS} />;}ہر پرامس فی سیشن کیش ہوتا ہے اور اس کی کلید کمپوننٹ اور پراپس سے بنتی ہے، اس لیے دوبارہ رینڈر کرنے پر پہلے سے جاری درخواست دوبارہ استعمال ہوتی ہے۔ روٹ بدلنے اور router.refresh() پر کیش صاف ہوتا ہے۔ اِن پٹ کے بعد ڈیٹا تک رسائی دوبارہ چلانے کے لیے router.refresh() کال کریں:
const router = useRouter();useInput((e) => e.input === "r" && router.refresh());ایسِنک کمپوننٹس کو دیے گئے پراپس سیریلائز ہونے کے قابل ہونے چاہئیں: اسٹرنگز، نمبرز، سادہ آبجیکٹس، اَریز، Date، URL، Map، Set اور ایلیمنٹس۔ فنکشن پراپس کلید کا حصہ نہیں ہوتے (ڈیٹا دیں، کال بیکس نہیں)۔ ہر سیشن زیادہ سے زیادہ 256 پرامس کیش کرتا ہے۔ آپ @textjs/app/server-components کے serverComponent(fn) سے کمپوننٹ کو خود بھی ریپ کر سکتے ہیں۔
ہکس (useState، useInput، …) کسی async function کمپوننٹ کے اندر کام نہیں کرتے۔ تعامل کے لیے اس کے گرد یا نیچے ایک عام کمپوننٹ رینڈر کریں۔
کام کرنے کا طریقہ
browser tab ──(GET /__textjs/live, server-sent events)──▶ frame deltasbrowser tab ──(POST /__textjs/live, JSON)──────────────▶ keys, paste, wheel, resize, focus, Back/Forward- ہر ٹیب کا ایک سیشن۔ ہر صفحہ لوڈ ہونے پر اپنا سیشن کھولتا ہے: سرور پر ایک React روٹ، میموری میں راؤٹر ہسٹری اور ایک سیل بفر۔ سیشن آئی ڈیز 192-بٹ کی رینڈم قدریں ہیں جنہیں براؤزر صرف میموری میں رکھتا ہے۔
- سیل ڈیلٹاز۔ سرور صرف وہ قطاریں بھیجتا ہے جن کا ہیش اس کنکشن کو ملنے والے آخری فریم کے بعد بدلا ہو۔ ہر قطار صرف آخر میں اندراج جوڑنے والی اسٹائل ٹیبل کے مطابق متن کے سلسلوں کی صورت میں بھیجی جاتی ہے، اور چوڑے گرافیمز، ایموجی کلسٹرز اور نیم شفاف رنگ بالکل برقرار رہتے ہیں۔
- بیک پریشر۔ فریموں کے درمیان کم از کم 66 ms کا وقفہ ہوتا ہے، اور ہر فریم کا موازنہ ساکٹ کے آخری قبول کردہ مواد سے ہوتا ہے۔ جب سست کنکشن ڈیٹا لینا بند کرتا ہے، تو درمیانی حالتیں چھوڑ دی جاتی ہیں، اور اگلا فریم براہ راست تازہ ترین حالت پر پہنچتا ہے۔
- دوبارہ کنکشن۔ اگر اسٹریم منقطع ہو تو پلیئر
x-textjs-liveہیڈر میں اپنی سیشن آئی ڈی کے ساتھ دوبارہ جڑتا ہے (URL میں کبھی نہیں) اور نئے کی فریم سے اسی سیشن کو اس کی حالت برقرار رکھتے ہوئے دوبارہ جاری کرتا ہے۔ ایڈریس بار کی چھوٹی ہوئی تبدیلیاں دوبارہ چلائی جاتی ہیں۔ بغیر کنکشن والا سیشن رینڈرنگ روک دیتا ہے اور 30 سیکنڈ بعد اَن ماؤنٹ ہو جاتا ہے؛ اگر سرور اسے بھول چکا ہو تو پلیئر نیا سیشن کھولتا ہے۔ - ہسٹری۔ واپس اور آگے دونوں ہسٹریز کو یکساں تعداد میں اندراجات آگے پیچھے کرتے ہیں، اور سرور پر
router.back()براؤزر کی ہسٹری کو بھی پیچھے لے جاتا ہے۔ - سیکیورٹی۔ دونوں اینڈ پوائنٹس کراس سائٹ درخواستیں مسترد کرتے ہیں (
Sec-Fetch-SiteاورOrigin؛X-Forwarded-Hostصرفserver.trustProxyکے ساتھ شمار ہوتا ہے)۔ اِن پٹ POSTs کو JSON باڈی اورx-textjs-liveہیڈر درکار ہیں۔ ہر ایونٹ کی مقررہ اسکیما کے مطابق توثیق ہوتی ہے، اور نیویگیشن کے اہداف آپ کے اوریجن کے پاتھ ہونے چاہئیں۔ صرف آپ کےapp/مینی فیسٹ کے صفحات رینڈر ہو سکتے ہیں۔ سیشن ان کوکیز سے بندھے ہوتے ہیں جن کے ساتھ وہ کھولے گئے تھے۔ - حدود۔ فی POST زیادہ سے زیادہ 64 ایونٹس اور فی سیشن تقریباً 240 فی سیکنڈ (اس سے زیادہ پر
429)، باڈی کا 5 سیکنڈ کا ٹائم آؤٹ، فی کلائنٹ ایڈریس 32 سیشن، اور فی پراسیسmaxSessions(سب سے زیادہ دیر سے منقطع سیشن پہلے نکالا جاتا ہے)۔
export default defineConfig({ live: { maxSessions: 512, reconnectGraceMs: 60_000 },});جزوی پری رینڈرنگ
text build ہر اسٹیٹک روٹ کو مقررہ viewport سائز پر ایک بار رینڈر کرتا ہے اور اس پہلے فریم کو روٹ کے HTML میں ایک چھوٹے کلاسک <script> کے طور پر اِن لائن رکھتا ہے۔ کسی بھی ماڈیول کے لوڈ ہونے سے پہلے کونسول پینٹ ہوتا ہے:
- اسٹیٹک شیل۔ ہر وہ چیز جو سنکرونس طور پر رینڈر ہوتی ہے۔ انتظار کرنے والی ہر
<Suspense>باؤنڈری اپنا فال بیک دکھاتی ہے، بشمول ایسِنک سرور کمپوننٹس (جو شیل رینڈر ہوتے وقت نہیں چلتے)،use()اور لیزی ماڈیولز۔ شیل رینڈر ہوتے وقتfetch()کبھی حل نہیں ہوتا، اس لیے ایفیکٹس میں لوڈ ہونے والا ڈیٹا بھی اپنی لوڈنگ حالت میں رہتا ہے۔ - ڈائنامک خالی جگہیں۔ جب JavaScript شروع ہوتا ہے تو کلائنٹ رینڈرر یا فریم پلیئر اصل فریم اسٹریم کرتا ہے۔ اسے شیل کے نیچے آخر میں جوڑا جاتا ہے، اور ہائیڈریٹ ہونے والے فریم کا اپنا پردہ اسی DevTools پینٹ میں شیل کو نظروں سے ہٹا دیتا ہے۔ شیل اپنے پینٹ ہونے کا وقت ریکارڈ کرتا ہے، اس لیے پہلا ہائیڈریٹ ہونے والا فریم سست پاتھ پر جانے کے بجائے DevTools کی صفائی کے بعد والی مدت ختم ہونے کا انتظار کرتا ہے۔
لوڈ کے دوران، صرف شیل کونسول صاف کرتا ہے۔ ہائیڈریٹ ہونے والا رینڈرر آخر میں جوڑنے کے موڈ (initialClear: false) میں شروع ہوتا ہے، اس لیے کچھ نہیں جھلملاتا، اور شیل صرف اسی پاتھ پر پینٹ ہوتا ہے جس کے لیے اسے رینڈر کیا گیا تھا۔ ڈائنامک روٹس ([id]، force-dynamic) کوئی شیل رینڈر نہیں کرتے۔
✓ Prerendering console shells (5/5) · 1 server-rendered, frame player 56.5 kB (21.3 kB gzip)اسے بند کرنے کے لیے text.config.ts میں ppr: false رکھیں۔ پری رینڈرنگ آپ کے کمپوننٹس کو بلڈ کے وقت چلاتی ہے، اس لیے ماڈیول کے ٹاپ لیول کوڈ میں صرف براؤزر کے گلوبلز نہ رکھیں۔ جو روٹ پری رینڈرنگ کے دوران ایرر پھینکے، redirect() / notFound() کال کرے یا نیویگیٹ کرے، اسے کوئی شیل نہیں ملتا (ایرر اسکرین کبھی HTML میں شامل نہیں ہوتی)، اور بلڈ آپ کو وجہ بتاتا ہے۔
شیل viewport.width × viewport.height (ڈیفالٹ 120 × 36) پر رینڈر ہوتا ہے کیونکہ بلڈ وزیٹر کے کونسول کا سائز نہیں جان سکتا؛ پھر ہائیڈریٹ ہونے والا فریم اصل کونسول کے مطابق سائز بدلتا ہے۔ شیل ایک اِن لائن <script> ہے: اگر Content Security Policy اِن لائن اسکرپٹس کی اجازت نہ دے تو اسے ہیش سے اجازت دیں یا ppr: false رکھیں۔
ڈیپلائے کرنا
text build درج ذیل فائلیں لکھتا ہے:
| فائل | مقصد |
|---|---|
index.html، <route>/index.html | کلائنٹ روٹس اپنے شیلز کے ساتھ۔ کسی بھی اسٹیٹک ہوسٹ پر کام کرتے ہیں۔ |
assets/player-[hash].js | فریم پلیئر۔ |
.text/server/live.mjs | سرور روٹس، App Router اور کونسول رینڈرر۔ |
.text/pages/…، .text/live.html | سرور روٹ کی دستاویزات۔ |
.text/ansi/… | curl اور دیگر ٹرمینل کلائنٹس کے لیے پہلے فریم۔ |
text start اور bun اڈاپٹر ان سب کو سرو کرتے ہیں۔ سرور روٹس کو طویل وقت تک چلنے والا پراسیس درکار ہے: static، vercel اور cloudflare اڈاپٹرز بلڈ ناکام کر دیتے ہیں (TX030)۔