ساخت یک وبلاگ سفارشی از صفر در سال ۲۰۲۶ یک انتخاب آگاهانه است. اکثر نویسندگان فقط یک پلتفرم میزبانی‌شده را انتخاب می‌کنند و به کارشان ادامه می‌دهند. من تصمیم گرفتم وبلاگ تکنولوژی خود را با Astro بازسازی کنم، زیرا می‌خواستم مالک تمام لایه‌های پشته (stack) باشم و الگوهایی را یاد بگیرم که واقعاً یک سایت استاتیک را سریع‌تر می‌کنند، نه اینکه فقط متفاوت باشند. نتیجه، یک سایت دوزبانه است که محتوای ژاپنی و انگلیسی ارائه می‌دهد، به صورت پیش‌فرض هیچ جاوااسکریپتی ارسال نمی‌کند و تمام پیچیدگی‌ها را به جای مرورگر بازدیدکننده، روی سیستم من نگه می‌دارد.

در اینجا پنج الگویی که باعث موفقیت این پروژه شد، آورده شده است.

Content Collections با Zod

قابلیت Content Collections در Astro فراتر از سازماندهی فایل‌های Markdown در پوشه‌ها عمل می‌کند. این قابلیت یک قرارداد بین محتوا و کد شما برقرار می‌کند. من یک schema از Zod را به هر مجموعه مقاله پیوست کردم، به این معنی که مرحله build قبل از رندر شدن حتی یک صفحه، frontmatter را اعتبارسنجی می‌کند.

این schema به یک فیلد language نیاز دارد که فقط دو مقدار ja یا en را می‌پذیرد. هیچ ابهامی در مورد اینکه خواننده با چه زبانی روبرو خواهد شد، وجود ندارد. من همچنین یک فیلد pair اضافه کردم که ترجمه‌ها را به هم مرتبط می‌کند. اگر پستی درباره Astro به زبان ژاپنی منتشر کنم و بعداً آن را به انگلیسی ترجمه کنم، هر دو فایل دارای یک pair ID مشترک خواهند بود. این کار ساخت یک سوئیچر زبان (language switcher) را بسیار ساده می‌کند، زیرا رابطه در داده‌ها صریح است و از روی نام فایل‌ها استنباط نمی‌شود.

تاریخ‌ها از frontmatter در Markdown به صورت رشته (string) وارد می‌شوند، بنابراین schema به طور خودکار آن‌ها را به اشیاء واقعی Date تبدیل می‌کند. این کار منطق دستکاری رشته‌ها را در قالب‌های صفحه من حذف می‌کند. با این حال، مهم‌ترین مزیت، حالت خطا (failure mode) است. اگر فایلی فاقد یک فیلد ضروری باشد یا از کد زبان نامعتبر استفاده کند، فرآیند build بلافاصله با یک خطای واضح متوقف می‌شود. من آن را در ترمینال خود اصلاح می‌کنم، به جای اینکه پس از استقرار (deployment)، با یک چیدمان خراب یا خطای ۴۰۴ بی‌صدا مواجه شوم.

خط لوله تبدیل مقالات (Article Conversion Pipeline)

من از روز اول تمام پست‌ها را با این فرمت جدید ننوشتم. سال‌ها محتوا در Zenn و Dev.to قرار داشت که هر پلتفرم ویژگی‌های خاص و نحو (syntax) اختصاصی خود را دارد. به جای کپی-پیست کردن و اصلاح دستی، یک اسکریپت TypeScript نوشتم که دسته‌های کامل مقالات را به Markdown استاندارد تبدیل می‌کند.

Zenn از نحو callout سفارشی برای نکات و هشدارها استفاده می‌کند. اسکریپت من آن‌ها را به تگ‌های معنایی HTML یعنی aside تبدیل می‌کند تا در سراسر سایت به طور یکسان رندر شوند. Dev.to برای جاسازی‌ها (embeds) و بلوک‌های خاص به Liquid tags متکی است. این خط لوله آن‌ها را به لینک‌های ساده Markdown ترجمه می‌کند که در هر جایی کار می‌کنند.

برخی از پست‌ها شامل نکات جانبی هستند که فقط برای پلتفرم اصلی intended شده‌اند، مانند سلب مسئولیتی درباره دیوار پرداخت (paywall) Medium یا یک مسیر تصویر مخصوص Zenn. من آن‌ها را در کامنت‌های HTML قرار می‌دهم تا مبدل بتواند آن‌ها را در طول مهاجرت حذف کند. اسکریپت فایل‌ها را خط به خط پردازش می‌کند، اما به مرزهای کد احترام می‌گذارد. وقتی یک بلوک کد محصور (fenced code block) را تشخیص می‌دهد، قوانین تبدیل را کاملاً نادیده می‌گیرد. خراب کردن یک نمونه نحو (syntax) کل هدف یک وبلاگ تکنولوژی را از بین می‌برد، بنابراین پارسر خط به خط، بلوک‌های کد را به عنوان مناطق غیرقابل لمس در نظر می‌گیرد.

اکنون با اجرای یک دستور، سال‌ها نوشته بدون شکستن حتی یک لینک یا callout مجدداً منتشر می‌شوند.

تولید تصاویر OGP در زمان build

تصاویر اشتراک‌گذاری اجتماعی معمولاً چیزی هستند که بعداً به آن‌ها فکر می‌شود. شما یا آن‌ها را به صورت دستی طراحی می‌کنید یا یک سرویس runtime سنگین نصب می‌کنید که کارت‌ها را در لحظه تولید می‌کند. من هیچ‌کدام را نمی‌خواستم. هر تصویر Open Graph در این سایت در طول build تولید می‌شود تا بازدیدکنندگان چیزی بیش از یک تگ img سبک که به یک PNG استاتیک اشاره می‌کند، دریافت نکنند.

من از Satori استفاده می‌کنم که نشانه‌گذاری JSX را می‌گیرد و آن را به SVG رندر می‌کند. خروجی شفاف، قابل پیش‌بینی و برای قالب‌بندی آسان است. بهینه‌سازی واقعی از مدیریت فونت حاصل شد. یک فونت وب کامل ژاپنی می‌تواند به راحتی از پنج مگابایت فراتر رود. بارگذاری آن در طول build، چه برسد به اینکه از مرورگر بخواهید آن را دریافت کند، مضحک خواهد بود.

در عوض، من از subsetting در Google Fonts استفاده می‌کنم. اسکریپت متن عنوان را برای یک پست خاص بررسی می‌کند و فقط مجموعه دقیق glyph مورد نیاز برای رندر کردن آن رشته را درخواست می‌کند. اگر یک تیتر از چهل کاراکتر منحصر‌به‌فرد ژاپنی استفاده کند، فقط همان چهل کاراکتر از شبکه منتقل می‌شوند. فرآیند build سریع باقی می‌ماند و تصویر رندر شده هرگز بلوک‌های شکسته (tofu blocks) را نشان نمی‌دهد، زیرا زیرمجموعه (subset) دقیق است. هیچ چیز به شانس در زمان runtime واگذار نشده است.

حالت تاریک (Dark Mode) از طریق توکن‌های Tailwind

من از تزئین کردن هر المان با کلاس‌های utility dark: خودداری کردم. آن رویکرد مقیاس‌پذیری ضعیفی دارد و نشانه‌گذاری (markup) شما را با نویز پر می‌کند. من خودِ توکن‌های رنگ را بازتعریف کردم تا یک نام کلاس یکسان، بسته به تم فعال، به مقادیر متفاوتی ختم شود.

من برای هر سطح (surface) و رنگ متن از ویژگی‌های سفارشی CSS استفاده می‌کنم. در حالت روشن، --color-white به #ffffff نگاشت می‌شود. در حالت تاریک، همان نام متغیر به مقداری نزدیک به سیاه اشاره می‌کند. HTML من کاملاً مستقل (agnostic) باقی می‌ماند. یک کارت می‌تواند بدون توجه به زمان روز، از bg-ui-surface و text-ui-primary استفاده کند. تغییر تم، تعاریف متغیرها را در ریشه (root) تغییر می‌دهد و کل رابط کاربری بلافاصله واکنش نشان می‌دهد.

تنها ریسک این رویکرد، پرش ناگهانی محتوای روشن (flash of light content) پیش از بارگذاری استایل‌شیت‌ها است. من این مشکل را با یک اسکریپت کوچک درون‌خطی (inline script) در head سند حل کردم. این اسکریپت پیش از اولین رندر (first paint) اجرا می‌شود، localStorage و تنظیمات سیستم را بررسی می‌کند و بلافاصله ویژگی داده (data attribute) صحیح را تنظیم می‌کند. از آنجایی که اسکریپت رندر را تنها برای چند میلی‌ثانیه متوقف می‌کند، کاربر هرگز پیش از فعال شدن حالت تاریک، شاهد یک پرش سفیدکننده آزاردهنده نخواهد بود.

معماری جزیره‌ای (Island Architecture) و Zero-JS

فرض اصلی Astro این است که یک صفحه باید به عنوان HTML استاتیک شروع شود. جاوااسکریپت تنها زمانی وارد عمل می‌شود که یک تعامل واقعاً به آن نیاز داشته باشد. من این موضوع را جدی گرفتم.

من برای منوی سراسری و سوئیچ تغییر تم از React استفاده نکردم. هر دو با مقدار کمی vanilla JavaScript که در یک ماژول واحد قرار دارد، مدیریت می‌شوند. هیچ بار اضافی برای hydration، هیچ فرآیند diffing در virtual DOM و هیچ زمان اجرای فریم‌ورک (runtime) برای دانلود وجود ندارد.

تنها کتابخانه سنگینی که استفاده می‌کنم Mermaid.js برای رندر کردن نمودارها از متن است. به جای وارد کردن (import) آن به صورت سراسری، آن را درون یک Intersection Observer قرار دادم. این ناظر (observer) کانتینرهای نمودار را زیر نظر می‌گیرد. وقتی کاربر در فاصله چند صد پیکسلی از یکی از آن‌ها اسکرول می‌کند، اسکریپت به صورت پویا ماژول Mermaid را تزریق کرده و نمودار را رندر می‌کند. اگر پستی شامل نمودار نباشد، آن کتابخانه هرگز با شبکه درگیر نمی‌شود. بارگذاری اولیه صفحه سبک باقی می‌ماند و مرورگر فقط هزینه چیزی را می‌پردازد که خواننده واقعاً می‌بیند.

طرز فکر اولویت با ساخت (Build-First Mindset)

رشته‌ای که در تمام این الگوها جریان دارد ساده است: اگر می‌توانید کار را در مرحله ساخت (build) انجام دهید، همان‌جا انجامش دهید. داده‌های خود را پیش از استقرار (deploy) سایت، با Zod اعتبارسنجی کنید. سینتکس‌های اختصاصی پلتفرم را به جای زمان درخواست (request time)، از قبل تبدیل کنید. تصاویر شبکه‌های اجتماعی را به جای راه‌اندازی یک سرور، به فایل‌های استاتیک رندر کنید. رنگ‌های تم را از طریق توکن‌ها تعیین کنید، به جای اینکه منطق (logic) را به هر کلاینت ارسال کنید. اجرای جاوااسکریپت‌های سنگین را تا زمانی که کاربر واقعاً به آن‌ها نیاز داشته باشد، به تعویق بیندازید.

انتقال پیچیدگی به سمت چپ (leftward) یعنی به مرحله ساخت، باعث می‌شود زمان اجرا (runtime) قابل پیش‌بینی، حجم داده ارسالی (payload) کم و بار نگهداری مدیریت‌پذیر باقی بماند. سایت سریع باقی می‌ماند، نه به دلیل یک ترفند خاص، بلکه به این دلیل که به سادگی اتفاقات کمتری در مرورگر بازدیدکننده رخ می‌دهد. این پاداش واقعی انتخاب یک معماری استاتیک در سال ۲۰۲۶ است.