18 July, 2024

Làm lại blog - rời khỏi Wordpress

Không nhân dịp gì cả, mình quyết định code lại blog này. Không dùng Wordpress nữa, vậy mình dùng gì?

Làm lại blog - rời khỏi Wordpress
Available in:
 English
 Vietnamese
Reading time: 6 min.
Table of content

    Mình viết blog từ khoảng 2016, đến nay đã có tầm 60 bài viết gì đó. Do dự mình sắp sang công ty mới, nên mình muốn sẽ tập trung nhiều thời gian hơn vào việc chia sẻ kinh nghiệm và góc nhìn cá nhân qua việc viết lách.

    Để tập tối ưu công việc này, mình cần có nhiều tính năng đặc biệt hơn trên blog. Tuy vậy, mình nhận ra rằng phiên bản hiện tại, chạy trên Wordpress, có khá nhiều hạn chế.

    Vậy nếu không dùng Wordpress nữa, thì mình dùng cái gì?

    (Ảnh: giao diện hồi dùng Wordpress)

    Lên ý tưởng

    Vì sao mình bỏ dùng Wordpress?

    Thành thật mà nói thì Wordpress không hề tệ. Nhưng sự thật là công nghệ nào thì cùng có giới hạn cụ thể của nó. Bạn sẽ thấy, hầu hết các lý do mà mình đưa ra sau đây đều do mình yêu cầu các tính năng hơi "nâng cao" mà trên Wordpress làm được, nhưng chỉ là không thể tốt bằng tự code:

    Đa ngôn ngữ: Mặc dù các plugin như Polylang có thể tàm tạm (mình có dùng plugin này ở 2 website khác rồi), nhưng làm việc với Polylang thường khá tốn thời gian setup. Ở trang chủ ngxson.com mình đã phải tự code hết toàn bộ phần đa ngôn ngữ.

    Hỗ trợ markdown: Lý do này thì đơn giản hơn, càng ngày mình càng dùng markdown nhiều, nên chuyển hẳn sang một nền tảng khác như Jekyll là điều dễ hiểu

    Mở rộng tương tác: Lấy ý tưởng từ những blog khoa học cho phép người dùng tương tác trực tiếp trang web, mình muốn sau này có thể làm vài thứ cool như biểu diễn 3D chẳng hạn. Ví dụ về một bài viết có thể tương tác: https://distill.pub/2017/feature-visualization/

    Tốc độ: Mặc dù có thể cài một đống plugin và cache để làm Wordpress nhanh hơn, nhưng mình nghĩ là thà dùng cái khác nhẹ hơn, hiệu quả cao hơn về lâu dài.

    Kế hoạch

    Một vài yêu cầu nhất thiết phải có ở blog mới này:

    • Đa ngôn ngữ (một cách hiệu quả nhất có thể)
    • Dựa trên markdown
    • Giao diện hiện đại, đơn giản và trực quan
    • Nhẹ về dung lượng, nhanh về tốc độ
    • Có tính mở rộng về lâu dài

    Dựa vào danh sách trên, mình liệt kê ra hướng đi mà mình có thể chọn

    • Lập trình dựa trên NextJS và build thành static file (host trên CDN)
    • Dùng MDX, một bản mở rộng từ markdown, cho phép viết một vài code ReactJS ngay trên markdown
    • Mỗi bài viết một file, mỗi ngôn ngữ cùng thành một file riêng
    • Dùng daisyUItailwindcss để duy trì thiết kế giao diện ổn định về lâu dài
    • Có thể dùng AI để dịch bài viết sang các ngôn ngữ khác

    Bắt tay vào làm!

    Design trên Figma

    Bước đầu tiên mình làm không phải lao vào code, mà mở Figma lên để tạo Moodboard (hiểu nôm na như một trang để copy-paste các ý tưởng vào tham khảo)

    Sau khi tham khảo một hồi, mình cảm thấy khá ấn tượng với thiết kế của Medium, OpenAI blog và Apple Newsroom. Các trang này vừa đơn giản mà lại vừa mang lại sự tiện dụng.

    Với font chữ, mình chọn sự kết hợp giữa font serif và sans-serif để gợi chút hoài cổ:

    Tiếp theo, mình thiết kế thử xem blog của mình nên nhìn trông thế nào:

    Khá ổn nhỉ? Giờ thì code thôi.

    Setup NextJS

    Việc đầu tiên cần làm đó là setup một khung xương (framework) để quản lý và render file markdown thành HTML. Mình sẽ cần sử dụng generateStaticParams của NextJS để làm việc này.

    Code trôn nôm na thế này, với DataSource là class mình dùng để liệt kê tất cả các file:

    export async function generateStaticParams() {
      return await ParamsGenerator.article();
    }
    
    // ParamsGenerator
    export const ParamsGenerator = {
      async article(): Promise<ParamsArticle[]> {
        const articles = await DataSource.getAll();
        return articles.map((p) => ({ slug: p.slug }));
      },
      // ...
    }
    

    Ngoài ra còn có code để quản lý đa ngôn ngữ, chuyên mục, phân trang,... Logic nói ra thì không quá phức tạp, nhưng đến lúc bắt tay vào code sẽ thấy nhiều thứ khá hay phần thực hiện.

    Code giao diện

    Mình dùng daisyUItailwindcss:

    Khá may là các components có sẵn khá gần với những gì mình cần, nên cũng không phải sửa thêm quá nhiều. Ví dụ với card để chứa bài viết, code trông như sau:

    <Link
      href={getArticleURL(article)}
      className={`group card bg-base-200 w-full shadow-xl mt-4 hover:cursor-pointer`}
    >
      <figure>
        <img
          className={`group-hover:scale-105 duration-100 aspect-social object-cover`}
          src={article.coverImg}
          alt={article.title}
        />
      </figure>
      <div className="card-body">
        <p className="flex-grow-0">{CATEGORY[article.category[0]].name}</p>
        <h2 className={`flex-grow font-serif font-bold text-2xl`}>
          {article.title}
        </h2>
        <div className="card-actions justify-end flex-col">
          <p>{formatDateToStr(article.datePublish)}</p>
        </div>
      </div>
    </Link>
    

    Chuyển hết bài viết cũ sang markdown

    Mộ trong những vấn đề lớn nhất mình gặp phải đó là các bài viết cũ hoàn toàn dùng Wordpress thuần. Chuyển bằng tay sang markdown khá cực nhọc, nên mình viết một script để tự động hóa, sau đó dùng node-html-markdown để chuyển HTML thành markdown.

    Do kích cỡ ảnh ở blog cũ và blog mới khác nhau, nên mình cùng nhờ Claude AI viết một script nhỏ để làm ảnh cover to ra, còn nền thì dùng ảnh mờ, ví dụ thế này:

    Dịch bài viết nhờ Claude AI

    Mình không dùng ChatGPT vì mình cảm thấy chất lượng dịch không được "mượt" bằng. Hiện tại, có khoảng 8 bài viết đã có phiên bản tiếng Anh.

    Bonus: gợi ý bài viết bằng vector

    Yêu cầu đơn giản: cuối mỗi bài viết, mình muốn hiện gợi ý các bài khác có cùng chủ đề. Tất nhiên mình có thể làm bằng các chọn random theo chủ đề, nhưng nó không thú vị.

    Cách mình làm ở đây, đó là mình sẽ tính embedding vector (hiểu nôm na là vector chứa ý nghĩa) của bài viết, sau đó tính khoảng cách xem vector nào gần nhất. Nếu vector gần nhau thì suy ra 2 bài viết có ý nghĩa cũng gần nhau, và vì vậy có thể dùng làm gợi ý.

    Mình dùng OpenAI Embeddings API để làm việc này:

    Deploy lên server

    Bước cuối cùng, mình deploy lên Netlify và link vào domain của mình. Quá trình build diễn ra trực tiếp trên cloud, và cũng khá nhanh (thường thì khoảng 1 phút):

    Thành quả

    Vậy là sau 5 tối ngồi làm, cuối cùng mình cũng hoàn thành. Công việc không quá khó về kỹ thuật, chỉ là nó hơi nhiều công đoạn, và có những bước mình buộc phải làm thủ công.

    Nhưng nhờ có vậy mà blog của mình giờ sẵn sàng để có những bài viết chất lượng hơn. Các bạn hãy chú ý đón xem nhé!

    Want to receive latest articles from my blog?