২০২৬ সালে একদম শুরু থেকে একটি কাস্টম ব্লগ তৈরি করা একটি সুচিন্তিত সিদ্ধান্ত। বেশিরভাগ লেখক কেবল একটি হোস্টেড প্ল্যাটফর্ম বেছে নেন এবং কাজ শেষ করেন। আমি আমার টেক ব্লগটি Astro দিয়ে পুনরায় তৈরি করার সিদ্ধান্ত নিয়েছি কারণ আমি স্ট্যাকের প্রতিটি স্তর নিজের নিয়ন্ত্রণে রাখতে চেয়েছিলাম এবং এমন কিছু প্যাটার্ন শিখতে চেয়েছিলাম যা একটি স্ট্যাটিক সাইটকে কেবল ভিন্ন নয়, বরং আরও দ্রুত করে তোলে। এর ফলাফল হলো একটি দ্বিভাষিক সাইট যা জাপানি এবং ইংরেজি কন্টেন্ট পরিবেশন করে, ডিফল্টভাবে জিরো জাভাস্ক্রিপ্ট (zero JavaScript) পাঠায় এবং ভিজিটরের ব্রাউজারের পরিবর্তে সমস্ত জটিলতা আমার নিজের মেশিনে সীমাবদ্ধ রাখে।

এখানে সেই পাঁচটি প্যাটার্ন দেওয়া হলো যা এটিকে সফল করেছে।

Zod দিয়ে Content Collections

Astro-র Content Collections শুধুমাত্র Markdown ফাইলগুলোকে ফোল্ডারে সাজানোর চেয়েও বেশি কিছু করে। এগুলো আপনার কন্টেন্ট এবং কোডের মধ্যে একটি চুক্তি (contract) নিশ্চিত করে। আমি প্রতিটি আর্টিকেল কালেকশনের সাথে একটি Zod schema যুক্ত করেছি, যার মানে হলো একটি পেজ রেন্ডার হওয়ার আগেই বিল্ড স্টেপটি frontmatter যাচাই করে নেয়।

এই স্কিমাটিতে একটি language ফিল্ড প্রয়োজন যা কেবল দুটি মান গ্রহণ করে: ja অথবা en। পাঠক কোন ভাষা পাবেন তা নিয়ে কোনো অস্পষ্টতা থাকে না। আমি একটি pair ফিল্ডও যোগ করেছি যা অনুবাদগুলোকে একে অপরের সাথে যুক্ত করে। আমি যদি জাপানি ভাষায় Astro সম্পর্কে একটি পোস্ট প্রকাশ করি এবং পরে সেটি ইংরেজিতে অনুবাদ করি, তবে উভয় ফাইলের একই pair ID থাকবে। এটি একটি ল্যাঙ্গুয়েজ সুইচার তৈরি করাকে অত্যন্ত সহজ করে তোলে কারণ সম্পর্কটি ডেটার মধ্যে স্পষ্টভাবে থাকে, ফাইলের নামের ওপর নির্ভর করে না।

Markdown frontmatter থেকে তারিখগুলো স্ট্রিং হিসেবে আসে, তাই স্কিমাটি সেগুলোকে স্বয়ংক্রিয়ভাবে আসল Date অবজেক্টে রূপান্তর করে। এটি আমার পেজ টেমপ্লেটের ভেতরে স্ট্রিং ম্যানিপুলেশন করার প্রয়োজনীয়তা দূর করে দেয়। তবে সবচেয়ে গুরুত্বপূর্ণ সুবিধা হলো এর failure mode। যদি কোনো ফাইলে প্রয়োজনীয় ফিল্ড না থাকে বা ভুল ল্যাঙ্গুয়েজ কোড ব্যবহার করা হয়, তবে বিল্ডটি সাথে সাথে একটি স্পষ্ট এরর (error) দিয়ে ক্র্যাশ করে। এর ফলে ডেপ্লয়মেন্টের পর কোনো ভাঙা লেআউট বা সাইলেন্ট 404 এরর খুঁজে বের করার বদলে আমি আমার টার্মিনালেই তা ঠিক করে নিতে পারি।

Article Conversion Pipeline

আমি প্রথম দিন থেকেই প্রতিটি পোস্ট এই নতুন ফরম্যাটে লিখিনি। বছরের পর বছর ধরে আমার কন্টেন্ট Zenn এবং Dev.to-তে ছিল, যেখানে প্রতিটি প্ল্যাটফর্মের নিজস্ব বৈশিষ্ট্য এবং সিনট্যাক্স রয়েছে। কপি-পেস্ট করে হাতে ঠিক করার পরিবর্তে, আমি একটি TypeScript স্ক্রিপ্ট লিখেছি যা আর্টিকেলের পুরো ব্যাচকে স্ট্যান্ডার্ড Markdown-এ রূপান্তর করে।

Zenn টিপস এবং ওয়ার্নিংয়ের জন্য কাস্টম callout সিনট্যাক্স ব্যবহার করে। আমার স্ক্রিপ্ট সেগুলোকে সিম্যান্টিক HTML aside ট্যাগে রূপান্তর করে যাতে সাইটজুড়ে সেগুলো একইভাবে রেন্ডার হয়। Dev.to এমবেড এবং বিশেষ ব্লকের জন্য Liquid ট্যাগ ব্যবহার করে। এই পাইপলাইনটি সেগুলোকে সাধারণ Markdown লিঙ্কে রূপান্তর করে যা যেকোনো জায়গায় কাজ করে।

কিছু পোস্টে এমন কিছু কথা (asides) থাকতে পারে যা কেবল মূল প্ল্যাটফর্মের জন্য ছিল, যেমন Medium পেওয়াল সম্পর্কে কোনো ডিসক্লেইমার বা Zenn-নির্দিষ্ট কোনো ইমেজ পাথ। আমি সেগুলোকে HTML কমেন্ট হিসেবে মুড়িয়ে দিই যাতে মাইগ্রেশনের সময় কনভার্টার সেগুলো বাদ দিয়ে দিতে পারে। স্ক্রিপ্টটি ফাইলগুলো লাইন বাই লাইন প্রসেস করে, তবে এটি কোড বাউন্ডারি বা সীমানা বজায় রাখে। যখন এটি একটি fenced code block শনাক্ত করে, তখন এটি ট্রান্সফরমেশন রুলগুলো পুরোপুরি এড়িয়ে যায়। একটি টেক ব্লগের মূল উদ্দেশ্যই হলো সঠিক কোড দেখানো, তাই সিনট্যাক্স স্যাম্পল নষ্ট হয়ে গেলে পুরো বিষয়টিই ব্যর্থ হবে; তাই লাইন-বাই-লাইন পার্সার কোড ব্লকগুলোকে স্পর্শ না করার মতো একটি জোন হিসেবে বিবেচনা করে।

এখন মাত্র একটি কমান্ড চালালেই কোনো লিঙ্ক বা callout নষ্ট না করেই বছরের পর বছর লেখা পুনরায় প্রকাশ করা সম্ভব।

Build-Time OGP Image Generation

সোশ্যাল শেয়ারিং ইমেজগুলো সাধারণত afterthought হিসেবে দেখা হয়। আপনি হয় সেগুলো ম্যানুয়ালি ডিজাইন করেন অথবা একটি ভারী রানটাইম সার্ভিস ইনস্টল করেন যা চাহিদা অনুযায়ী কার্ড তৈরি করে। আমি এই দুটির কোনটিই চাইনি। এই সাইটের প্রতিটি Open Graph ইমেজ বিল্ডের সময় তৈরি করা হয় যাতে ভিজিটররা একটি স্ট্যাটিক PNG-এর দিকে নির্দেশকারী একটি হালকা img ট্যাগ ছাড়া আর কিছুই না পায়।

আমি Satori ব্যবহার করি, যা JSX মার্কআপ গ্রহণ করে এবং সেটিকে SVG-তে রেন্ডার করে। এর আউটপুট নিখুঁত, অনুমানযোগ্য এবং টেমপ্লেট করা সহজ। আসল অপ্টিমাইজেশনটি এসেছে ফন্ট হ্যান্ডলিং থেকে। একটি পূর্ণাঙ্গ জাপানি ওয়েব ফন্টের সাইজ সহজেই পাঁচ মেগাবাইটের বেশি হতে পারে। বিল্ডের সময় সেটি লোড করা তো দূরের কথা, ব্রাউজারকে সেটি ফেচ (fetch) করতে বলাটা হবে অযৌক্তিক।

এর পরিবর্তে, আমি Google Fonts subsetting ব্যবহার করি। স্ক্রিপ্টটি একটি নির্দিষ্ট পোস্টের টাইটেল টেক্সট পরীক্ষা করে এবং সেই স্ট্রিংটি রেন্ডার করার জন্য ঠিক যতটুকু গ্লিফ সেট (glyph set) প্রয়োজন কেবল সেটুকুই রিকোয়েস্ট করে। যদি একটি হেডলাইনে চল্লিশটি অনন্য জাপানি অক্ষর থাকে, তবে কেবল সেই চল্লিশটি অক্ষরই নেটওয়ার্কের মাধ্যমে আসবে। এর ফলে বিল্ড দ্রুত থাকে এবং রেন্ডার করা ইমেজে কখনোই ভাঙা 'tofu blocks' দেখা যায় না কারণ সাবসেটটি অত্যন্ত নিখুঁত। রানটাইমের কোনো অনিশ্চয়তার ওপর কিছু ছেড়ে রাখা হয়নি।

Tailwind Tokens-এর মাধ্যমে Dark Mode

আমি প্রতিটি এলিমেন্টকে dark: ইউটিলিটি ক্লাস দিয়ে সাজাতে অস্বীকার করেছি। এই পদ্ধতিটি বড় প্রজেক্টের ক্ষেত্রে স্কেল করা কঠিন এবং আপনার মার্কআপকে অপ্রয়োজনীয় কোড দিয়ে ভরিয়ে ফেলে। আমি নিজেই কালার টোকেনগুলোকে পুনরায় সংজ্ঞায়িত করেছি যাতে একটিভ থিমের ওপর ভিত্তি করে একই ক্লাস নেম ভিন্ন ভিন্ন মান প্রদান করে।

আমি প্রতিটি সারফেস এবং টেক্সট কালারের জন্য CSS custom properties ব্যবহার করি। লাইট মোডে, --color-white ম্যাপ করে #ffffff-এ। ডার্ক মোডে, একই ভেরিয়েবল নাম একটি প্রায় কালো (near-black) মানের দিকে নির্দেশ করে। আমার HTML সম্পূর্ণ নিরপেক্ষ থাকে। একটি কার্ড দিনের সময়ের কথা চিন্তা না করেই bg-ui-surface এবং text-ui-primary ব্যবহার করতে পারে। থিম সুইচ রুট (root) লেভেলে ভেরিয়েবল ডেফিনিশনগুলো পরিবর্তন করে দেয়, এবং পুরো ইন্টারফেস তাৎক্ষণিকভাবে সাড়া দেয়।

এই পদ্ধতির একটি ঝুঁকি হলো স্টাইলশিট লোড হওয়ার আগে হঠাৎ উজ্জ্বল কন্টেন্ট ভেসে ওঠা (flash of light content)। আমি এটি ডকুমেন্টের head-এ একটি ছোট ইনলাইন স্ক্রিপ্টের মাধ্যমে সমাধান করেছি। এটি প্রথম পেইন্টের (first paint) আগে চলে, localStorage এবং সিস্টেম প্রেফারেন্স চেক করে এবং তাৎক্ষণিকভাবে সঠিক data attribute সেট করে দেয়। যেহেতু স্ক্রিপ্টটি রেন্ডার করার প্রক্রিয়াকে মাত্র কয়েক মিলিসেকেন্ডের জন্য থামিয়ে রাখে, তাই ডার্ক মোড চালু হওয়ার আগে ভিজিটর কখনও কোনো বিরক্তিকর সাদা আলোর ঝলকানি দেখেন না।

Island Architecture এবং Zero-JS

Astro-এর মূল ধারণা হলো একটি পেজকে স্ট্যাটিক HTML হিসেবে শুরু হওয়া উচিত। JavaScript কেবল তখনই আসে যখন কোনো ইন্টারঅ্যাকশনের জন্য সত্যিই প্রয়োজন হয়। আমি এটিকে গুরুত্বের সাথে নিয়েছি।

আমি গ্লোবাল মেনু এবং থিম টোগল করার জন্য React ব্যবহার করা এড়িয়ে চলেছি। উভয়ই একটি মাত্র মডিউলে থাকা সামান্য পরিমাণ vanilla JavaScript দিয়ে পরিচালনা করা হয়। এখানে কোনো hydration overhead নেই, কোনো virtual DOM diffing নেই এবং ডাউনলোড করার জন্য কোনো framework runtime নেই।

আমি টেক্সট থেকে ডায়াগ্রাম রেন্ডার করার জন্য একমাত্র ভারী লাইব্রেরি হিসেবে Mermaid.js ব্যবহার করি। এটিকে গ্লোবাল ইম্পোর্ট করার পরিবর্তে, আমি এটিকে একটি Intersection Observer-এর ভেতরে র‍্যাপ (wrap) করেছি। অবজারভারটি ডায়াগ্রাম কন্টেইনারগুলোর ওপর নজর রাখে। যখন একজন ব্যবহারকারী একটি ডায়াগ্রামের কয়েকশ পিক্সেলের মধ্যে স্ক্রল করেন, তখন স্ক্রিপ্টটি ডায়নামিকভাবে Mermaid মডিউলটি ইনজেক্ট করে এবং ডায়াগ্রামটি রেন্ডার করে। যদি কোনো পোস্টে কোনো ডায়াগ্রাম না থাকে, তবে সেই লাইব্রেরিটি কখনোই নেটওয়ার্ক ব্যবহার করে না। প্রাথমিক পেজ লোড হালকা থাকে এবং ব্রাউজার কেবল সেই অংশটুকুর জন্যই রিসোর্স খরচ করে যা পাঠক আসলে দেখেন।

Build-First Mindset

প্রতিটি প্যাটার্নের মধ্য দিয়ে প্রবাহিত মূল কথাটি হলো সহজ: যদি আপনি বিল্ড (build) করার সময় কাজটি করতে পারেন, তবে সেখানেই তা করুন। সাইট ডেপ্লয় করার আগে Zod দিয়ে আপনার ডেটা ভ্যালিডেট করুন। রিকোয়েস্ট টাইমে করার পরিবর্তে আগেভাগেই প্রোপাইটরি প্ল্যাটফর্ম সিনট্যাক্স (proprietary platform syntax) কনভার্ট করে নিন। একটি সার্ভার চালু করার পরিবর্তে সোশ্যাল ইমেজগুলোকে স্ট্যাটিক ফাইলে রেন্ডার করুন। প্রতিটি ক্লায়েন্টে লজিক পাঠানোর পরিবর্তে টোকেনের মাধ্যমে থিম কালার সমাধান করুন। ভারী JavaScript ব্যবহারকারীর প্রয়োজন না হওয়া পর্যন্ত স্থগিত রাখুন।

জটিলতাকে বিল্ড স্টেপে (build step) স্থানান্তরিত করার ফলে রানটাইম (runtime) অনুমানযোগ্য থাকে, পেলোড (payload) ছোট থাকে এবং রক্ষণাবেক্ষণের বোঝা নিয়ন্ত্রণযোগ্য থাকে। সাইটটি দ্রুত থাকে কোনো একটি বিশেষ কৌশলের কারণে নয়, বরং ভিজিটরের ব্রাউজারের ভেতরে খুব কম কাজ সম্পন্ন হয় বলে। ২০২৬ সালে একটি স্ট্যাটিক আর্কিটেকচার বেছে নেওয়ার আসল সুফল এটাই।