وب سایت شخصی + وبلاگ

وب سایت شخصی + وبلاگ

در این مقاله در رابطه با مسائل فنی و تصمیمات اخذ شده در طراحی این وب سایت (وب سایت شخصی من) صحبت خواهیم کرد. مباحث بررسی شده در این مقاله از تکنولوژی های انتخاب شده برای ساخت بخش های کوچک و بزرگ سایت تا فلسفه ی تصمیمات اخذ شده در طراحی کامپوننت ها، انتخاب تکنولوژی های مختلف و رفتار سایت در برخورد با بعضی کاربران را شامل می شود.

 

اهداف اصلی ساخت این وب سایت با تمام وقتی که از من می گرفت چند مورد خاص بود که هزینه اش را در نظر من جبران می کرد: هدف اول عملکرد آن به عنوان یک پورتفولیو آماده بود. این وب سایت به همراه دیگر پروژه هایی که در آن لیست کرده ام به عنوان یک پورتفولیو آماده برای کارفرمایان عمل می کند تا بتوانند فرآیند فکری من در توسعه ی نرم افزار را مشاهده کنند. مسئله ی دوم علاقه ی شخصی من به حوزه ی توسعه ی وب است. شخصا علاقه دارم که در رابطه با حوزه ی برنامه نویسی وب و برنامه نویسی به طور کلی (مانند برنامه های موبایل و سیستمی) مطالبی را نوشته و به اشتراک بگذارم. به همین دلیل است که بخش «بلاگ» را برای این وب سایت طراحی کرده ام.

در این بخش در رابطه با تکنولوژی های استفاده شده و دلیل انتخاب آن ها صحبت خواهم کرد.

این وب سایت روی فریم ورک Next.js (نسخه ی ۱۴) پیاده شده است که خودش از React 18 و Node.js استفاده می کند. دلیل انتخاب NextJS ساده سازی فرآیند توسعه و طراحی برنامه های وب امروزی (مخصوصا مسئله ی caching) است. البته استفاده از این فریم ورک و به طور کلی فریم ورک های جامع React که به نام meta framework شناخته می شوند (مانند Remix و Next و Gatsby) توسط تیم اصلی React به جای استفاده از React به تنهایی پیشنهاد شده است. در واقع اگر به وب سایت رسمی React سری بزنید این توصیه را در بخش «شروع یک پروژه جدید» مشاهده می کنید. همچنین توسعه دهندگان هسته ی React چندین بار این موضوع را توییت کرده اند:

توصیه توسعه دهندگان هسته اصلی React به استفاده از Next.js
توصیه توسعه دهندگان هسته اصلی React به استفاده از Next.js

لازم به ذکر است که تمام این پروژه با typescript نوشته شده است.

برای استایل دهی از فریم ورک tailwind استفاده شده است که چند دلیل دارد. هنگام کار با پروژه های جاوا اسکریپتی باید یکی از دو نوع فریم ورک های استایل دهی را انتخاب کنید:

  • فریم ورک های عادی CSS که مستقلا و به صورت CSS عادی اجرا می شوند.
  • CSS-In-JS که برای اجرا نیاز به رانتایم جاوا اسکریپتی دارند.

فریم ورک هایی مانند styled-components که طرفداران خودش را هم دارد از دسته ی دوم هستند. با اینکه چنین فریم ورک هایی ویژگی های قابل توجهی دارند اما دو مشکل اصلی دارند که باعث شدند به شخصه از آن ها در این پروژه استفاده نکنم. مشکل اول پشتیبانی بسیاری از آن ها از سرور کامپوننت ها در Next.js است که مشکل کوچکی است چرا که هنوز بسیاری از آن ها مانند styled-components با Next.js 13 کاملا سازگار شده اند. مشکل دوم که مشکل اصلی من بود، اضافه کردن به بار پردازشی برنامه است. چنین فریم ورک هایی برای اجرا نیاز به یک رانتایم جاوا اسکریپتی مانند Node.js دارند و برای استایل دهی صفحات شما باید توسط جاوا اسکریپت پردازش شوند بنابراین روی سیستم های ضعیف باعث کندی و تاخیر در اجرا می شوند. به شخصه چنین فریم ورک هایی را کاملا دور از فلسفه ی اصلی CSS می دانم و علاقه ای به استفاده از آن ها ندارم.

 

از طرف دیگر فریم ورک هایی مانند tailwind قابلیت tree shake شدن را دارند، یعنی فقط کد هایی که استفاده کرده اید در پروژه ی نهایی شما قرار می گیرند و بقیه ی استایل های استفاده نشده حذف می شوند. معمولا بار شبکه ی فریم ورک هایی مانند tailwind (با احتساب مینیفای و gzip شدن حدود ۱۰ کیلوبایت است). حتی وب سایت Global Top 10 نت فلیکس که با tailwind نوشته شده است حدود ۶.۵ کیلوبایت حجم دارد! از طرفی این وب سایت یک وب سایت شخصی است و نیازی به فریم ورک های CSS-In-JS ندارد بلکه شخصا استفاده از آن ها را یک اشتباه بزرگ می دانم.

نظر به اینکه این وب سایت یک پروژه ی ساده و کوچک است و ترافیک بالایی ندارد به فکر استفاده از یک پایگاه داده ی خاص به نام Pocketbase کرده ام. این پایگاه داده در اصل از پایگاه داده ی SQLite در حالت WAL mode استفاده می کند و سپس یک wrapper با زبان Go برایش نوشته است تا کار توسعه دهندگان را آسان کند و برای پروژه های کوچک بی نظیر است چرا که قابلیت های آماده ی فراوانی را در اختیار توسعه دهنده می گذارد و زمان زیادی را از توسعه می کاهد. به طور مثال احراز هویت (authentication) به صورت خودکار در آن پیاده سازی شده است و همچنین از OAuth نیز پشتیبانی می کند. مدیریت فایل ها را به صورت محلی یا با استفاده از s3 به شکل خودکار انجام می دهد و برای مواردی مثل تولید thumbnail (تصاویر بند انگشتی) و تداخل نام فایل ها و امثال آن هیچ مشکلی نخواهید داشت. اعتبارسنجی داده ها (validation) بسیار سریع تر و ساده انجام می شود و نیازی به پیکربندی خاصی از سمت توسعه دهنده نیست. قابلیت ایجاد اتصال های realtime (مدل subscription) نیز در آن پیاده سازی شده است و به صورت خودکار یک پنل مدیریتی را برای ادمین می سازد که در آن همه چیز به واسطه ی یک UI بسیار تمیز در اختیار شما قرار دارد. از نظر عملکرد و قدرت نیز کاملا پاسخگوی پروژه های عادی است و بر اساس بنچ مارک های مختلف روی یک VPS ساده با چهار گیگ رم و ۲ هسته CPU می تواند بیشتر از ۱۰ هزار اتصال realtime را مدیریت کند چه برسد به درخواست های عادی! 

نمایی از پنل ادمین در پایگاه داده ی Pocketbase
نمایی از پنل ادمین در پایگاه داده ی Pocketbase

طبیعی است که چنین پایگاه داده ای محدودیت ها و معایب خودش را نیز دارد. به طور مثال هر wrapper ای که یک برنامه را احاطه کند باعث می شود کنترل جزئیات در سطح پایین از شما گرفته شود و دیگر دسترسی های خاص و جزئی که در PostgreSQL یا MySQL داشتید را از دست می دهید اما همانطور که عرض کردم برای پروژه هایی که دارای شرایط خاص و پیچیدگی یا وسعت زیاد نیستند بسیار مناسب است و بسیاری از عملیات های زمان بر را از سر راه بر می دارد.

اعتبار سنجی  در این پروژه از نظر دامنه ی اجرا دو بخش اصلی دارد: اعتبارسنجی در سمت کلاینت و در سمت سرور. هر نوع اعتبار سنجی که در سمت کاربر انجام شود تنها کارکرد UX و UI دارد و هیچ اعتبار امنیتی ندارد چرا که کلاینت به طور کامل در اختیار کاربر است بنابراین هر نوع محدودیت اعمال شده به سادگی قابل ویرایش توسط کاربر است. 

 

اعتبارسنجی در این سایت به طور کلی از ترکیب دو تکنولوژی Zod و Pocketbase پیاده سازی می شود. در ابتدا Zod برای اعتبارسنجی فرم ها مانند فرم ثبت نام (هم در سمت کلاینت و هم در سمت سرور) استفاده می شود. با انجام اعتبار سنجی در سمت کلاینت هم UX را ارتقاء می دهیم (کاربران غیرخرابکار به راحتی متوجه مشکل فرم پر شده می شوند) و هم بار اضافی پردازشی را از سرور خود دور می کنیم تا درخواست های تکراری و بیهوده به سمت سرور ارسال نشوند. تا این بخش مربوط به کاربران غیرخرابکار است اما اگر کاربری اعتبارسنجی سمت کلاینت را غیر فعال کند همان validation schema در سمت سرور نیز انجام می شود که با استفاده از Server Action ها در Next.js پیاده سازی شده است.

// Zod مثالی از اعتبارسنجی نام کاربری با کتابخانه ی
export const usernameType = z
.string()
.trim()
.min(3, { message: 'نام کاربری باید حداقل ۳ حرفی باشد.' })
.max(50, { message: 'نام کاربری باید حداکثر ۵۰ حرفی باشد.' })
.regex(/^[a-zA-Z0-9_-]+$/, {
message:
'نام کاربری باید فقط شامل حروف انگلیسی، اعداد و علامت های - و ـ باشد.',
});

از طرف دیگر یک لایه اعتبار سنجی دیگر در سمت Pocketbase وجود دارد تا اگر به نحوی داده های غیرمعتبر از Zod عبور کردند به سد پایگاه داده برخورد کنند. data type یا length و filte type و ... همگی در Pocketbase قابلیت تنظیم و محدود شدن دارند بنابراین جای نگرانی وجود ندارد. از آنجایی که احراز هویت توسط Pocketbase انجام می شود تمام مسائل مربوط به آن (مانند ثبت کامنت فقط برای کاربرانی که حساب کاربری دارند و آن را تایید نیز کرده اند) نیز توسط Pocketbase مدیریت می شود.

از آنجایی که Pocketbase یک پنل مدیریتی قدرتمند را در اختیار توسعه دهندگان می گذارد نیازی به ساخت پنل ادمین برای سایت از بین می رود (مگر در مواردی که نیاز به کنترل موارد سطح پایین دارید). به طور مثال من تمام پست های سایت را با استفاده از Rich Text Editor درون خود پنل ادمین Pocketbase می نویسم که در پس زمینه از TinyMCE استفاده می کند که یک Rich Text Editor بسیار پیشرفته است اما مشکلی وجود دارد: TinyMCE از سبک خودش برای نوشتن HTML استفاده می کند اما من ساختار خاصی را در کامپوننت های سایت طراحی کرده ام. به طور مثال استفاده از یک تصویر در TinyMCE باعث ساخت یک تگ ساده ی <img> می شود اما من از React استفاده می کنم و تمام تصاویری که در پست های بلاگ نمایش داده می شوند باید به خورد یک کامپوننت خاص بروند تا از تگ های <figure> و <figcaption> استفاده شود و استایل های خاص خودش را نیز بگیرد.

تگ ساده ی <img> باید درون تگ <a> قرار گرفته و یک آیکون زنجیر در کنار خود داشته باشد تا بتوان آن را به سرتیتر قابل تعامل تبدیل کرد
تگ ساده ی <img> باید درون تگ <a> قرار گرفته و یک آیکون زنجیر در کنار خود داشته باشد تا بتوان آن را به سرتیتر قابل تعامل تبدیل کرد

راه حل استفاده از یک parser است که با React سازگار باشد. parser ها محتوای HTML را که معمولا به صورت یک رشته (string) در پایگاه داده ذخیره می شود دریافت کرده و به شما اجازه می دهند انواع ویرایش ها را روی محتوای HTML در سمت سرور انجام بدهید. به عنوان یک مثال می توانید نحوه ی ویرایش تگ های <img> و قرار دادن آن ها را درون <figure> در کد زیر مشاهده کنید. من از پکیچ html-react-parser برای انجام این کار استفاده کرده ام:

// Type Guard: Check if the node is a image <Figure> with caption
const isImageFigure = (node: DOMNode): node is Element => {
return (
node.type === 'tag' &&
node.name === 'p' &&
node.attribs.class === 'BLOG_IMAGE_FIGURE'
);
};

// ---------> SET STYLE FOR IMAGE <Figure> AND ITS CAPTION
if (isImageFigure(node)) {
const image = node.firstChild as Element;
const { src, alt } = image.attribs;

return <BlogPostImage href={src} caption={alt} />;
}

با اینکه عملیات parse کردن HTML عملیات سبکی محسوب نمی شود اما ترکیب آن با caching مشکل را به طور کامل حل می کند. تمام مقالات سایت کش شده و به صورت Static در زمان build time تولید می شوند تا کاربر فقط یک صفحه HTML از قبل ساخته شده را مشاهده کند! با این حساب عملیات parse بسیار به ندرت اجرا خواهد شد.

پکیج Bright برای Syntax Highliting و نمایش کد در پست های بلاگ استفاده می شود (در بخش قبل برای نمایش کد های html-react-parser از آن استفاده کردیم. پکیج های زیادی برای Syntax Highliting و نمایش کد در React وجود دارند که معروف تر از Bright هستند اما Bright بهترین انتخاب برای این پروژه محسوب می شود چرا که قابلیت SSR دارد. یعنی با Server Component ها سازگار است بنابراین برخلاف پکیج های دیگر که حجم زیادی از جاوا اسکریپت را به باندل نهایی ما اضافه می کنند و روی دستگاه کاربر اجرا می شوند تا بتوانند کد ها را نشان بدهند، Bright توسط سرور همراه دیگر محتوای شما رندر می شود و هیچ جاوا اسکریپتی از سمت آن به سمت کاربر ارسال نمی شود. از طرفی caching باعث می شود دفعات بسیار کمی (فقط هنگام revalidate شدن یک مسیر در Next.js) اجرا شود و بار پردازشی سرور را بسیار کم می کند.

کار با زمان در این وب سایت بسیار محدود بوده و تعداد آن به هیچ عنوان زیاد نمی باشد بنابراین استفاده از کتابخانه هایی مانند moment و Jalali-date که حجم بالایی دارند هیچ گونه توجیهی نداشت. از طرفی نوشتن یک کتابخانه ساده نیز زمان بر بود و شخصا وقت نوشتن آن را نداشتم. خوشبختانه با dayjs آشنا شدم که تنها ۲ کیلوبایت حجم داشته و مخصوص چنین موقعیت هایی است بنابراین در این پروژه از آن استفاده شده است.

قسمت نظرات این سایت به کاربران اجازه می دهد نظرات خود را به صورت مارک داون نوشته و قبل از ثبت نظر آن را به صورت رندر شده (شکل نهایی) مشاهده کنند. برای ایجاد این قابلیت از پکیج معروف react-markdown به همراه پلاگین remark-gfm برای وسیع تر کردن قابلیت های آن استفاده شده است.

دو سربرگ «نوشتن دیدگاه» و «پیش نمایش دیدگاه» به کاربر اجازه می دهند قبل از ثبت نظر نتیجه ی نهایی را مشاهده کند.
دو سربرگ «نوشتن دیدگاه» و «پیش نمایش دیدگاه» به کاربر اجازه می دهند قبل از ثبت نظر نتیجه ی نهایی را مشاهده کند.

تصاویر به صورت خودکار توسط Next.js بهینه سازی می شوند که از قابلیت های عالی Next.js است اما کتابخانه ای که به صورت پیش فرض برای این کار استفاده می شود برای production (شکل نهایی وب سایت که روی سرور به صورت عمومی میزبانی می شود) به گفته ی تیم Next مناسب نیست بنابراین از کتابخانه ی بسیار معروف Sharp استفاده شده است.

با اینکه این پروژه نیاز آنچنانی به یک پکیج جداگانه برای مدیریت مرکزی state نداشت، تصمیم گرفتم در چند قسمت از برنامه (به طور مثال مدیریت مودال ها از چند کامپوننت مختلف) از jotai استفاده کنم که یک کتابخانه ی بسیار کوچک (حدود ۲ کلیوبایت) برای مدیریت state در برنامه های react محسوب می شود.

برای نمایش اعلان های کوچک (ارتقاء UX) در سر تا سر برنامه از پکیج بسیار محبوب react-hot-toast استفاده شده است. «تست» ها در این وب سایت شامل اعلانات، خطا ها، عملیات های موفقیت آمیز، حالت لودینگ و غیره می شوند. به طور کلی تست ها از جا به جایی محتوا یا به اصطلاح content shift جلوگیری کرده و تجربه ی کاربری را ارتقاء می دهند.

برای تغییر برنامه بین حالت روز و شب یا light mode و dark mode از پکیج next-themes استفاده شده که به طور خاص برای Next.js طراحی و بهینه سازی شده است. از آنجایی که Next.js کامپوننت های react را به دو دسته ی کامپوننت های کلاینت و کامپوننت های سرور تقسیم کرده است ایجاد حالت لایت و دارک به صورت یکپارچه بدون ایجاد فلش (لحظه هایی که ظاهر سایت با یک حالت نشان داده شده و پس از یک ثانیه به حالت اصلی برمی گردد) نیاز به توجه ویژه ای دارد که به لطف next-themes قابل انجام است.

برای نمایش confetti (شبیه به شرشره باران یا پولک باران) در هنگام برخی عملیات های خاص مانند لایک کردن پست ها در بخش «بلاگ» یا ثبت نام موفقیت آمیز با OAuth از پکیج react-confetti استفاده شده است.

در این بخش به صورت خلاصه به طراحی رابط کاربری (UI) می پردازیم. هدف اصلی این بخش آشنایی شما با طراحی کلی سایت بوده تا افراد بتوانند سریع و بدون ثبت نام، نگاهی کلی به UI سایت داشته باشند. همانطور که از ابتدا ذکر شد، برنامه با دو حالت لایت مود (حالت روز) و دارک مود (حالت شب) نوشته شده است بنابراین در هر بخش به هر دو حالت اشاره خواهیم کرد اما جهت جلوگیری از طولانی شدن مطلب فقط به ذکر چند مورد بسنده می کنیم. برای دریافت اطلاعات بیشتر می توانید به بخش های مختلف سایت یا سورس کد گیت هاب مراجعه کرده و شخصا بررسی نمایید.

در لینک های موجود در منوی nav موقعیت فعلی کاربر، که به active tab link یا لینک سربرگ شناخته شده، مشخص می شود:

لینک فعال (مسیر فعلی کاربر) با رنگ قرمز مشخص شده است
لینک فعال (مسیر فعلی کاربر) با رنگ قرمز مشخص شده است

در بخش کاربری نیز سه لینک اصلی وجود دارند که به ترتیب برای «جست و جو»، «منوی کاربری» و «دارک مود» هستند:

در لایت مود، پس زمینه نیمه شفاف طراحی شده است.
در لایت مود، پس زمینه نیمه شفاف طراحی شده است.

در صفحه ی اصلی سایت لیست مقالات آموزشی و پورتفولیو را مشاهده می کنید:

نمایی از مقالات سایت در حالت دارک و لایت
نمایی از مقالات سایت در حالت دارک و لایت
اطلاعات کاربر در سه سربرگ مختلف دسته بندی شده اند.
اطلاعات کاربر در سه سربرگ مختلف دسته بندی شده اند.

 

نوع احراز هویت (ایمیل یا استفاده از OAuth) نیز در این بخش به کاربر نمایش داده می شود.
نوع احراز هویت (ایمیل یا استفاده از OAuth) نیز در این بخش به کاربر نمایش داده می شود.

همچنین در حالت موبایل:

پنل اطلاعات کاربران کاملا ریسپانسیو طراحی شده است.
پنل اطلاعات کاربران کاملا ریسپانسیو طراحی شده است.

 

مقالات بلاگ قابلیت مرتب کردن سریع و همچنین لایک شدن، بوک مارک شدن، اشتراک گذاری و گزارش خطا را دارند که به صورت عمودی در سمت راست هر پست نمایش داده شده است.
مقالات بلاگ قابلیت مرتب کردن سریع و همچنین لایک شدن، بوک مارک شدن، اشتراک گذاری و گزارش خطا را دارند که به صورت عمودی در سمت راست هر پست نمایش داده شده است.

 

در بخش جست و جوی پیشرفته کاربران می توانند دایره ی جست و جو را محدود تر کنند.
در بخش جست و جوی پیشرفته کاربران می توانند دایره ی جست و جو را محدود تر کنند.
نوار جست و جوی پیشرفته در حالت active
نوار جست و جوی پیشرفته در حالت active
در هنگام مطالعه ی مقالات یک ستون کمکی در کنار صفحه برای تعامل کاربران وجود دارد.
در هنگام مطالعه ی مقالات یک ستون کمکی در کنار صفحه برای تعامل کاربران وجود دارد.
بخش نظرات از markdown پشتیبانی می کند.
بخش نظرات از markdown پشتیبانی می کند.
صفحه ی خطای 404 یا خطای NOT FOUND
صفحه ی خطای 404 یا خطای NOT FOUND

 

صفحه ی خطای 500 یا خطای Internal Server Error
صفحه ی خطای 500 یا خطای Internal Server Error

 

برای تجربه ی بهتر بهتر است از بخش های مختلف سایت دیدن نمایید. سورس کد این پروژه به صورت آزاد روی گیت هاب قرار دارد.