Xây dựng Blog Cá nhân chuyên nghiệp từ đầu với Next.js, Tailwind CSS và Vercel: Đừng để 2h sáng còn fix bug!

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Quick start: Làm ngay trong 5 phút, khỏi cần đọc nhiều!

Đã 2 giờ sáng rồi, server cứ rên rỉ còn cái bug kia thì liên tục hiện hồn. Ai còn tâm trí phân tích lý thuyết suông nữa? Chúng ta cần một giải pháp nhanh, gọn, lẹ để blog có thể hoạt động trơn tru ngay lập tức. Đây chính là lộ trình thần tốc giúp bạn đưa blog lên mạng chỉ trong tích tắc.

Bước 1: Khởi tạo dự án Next.js

Bạn mở terminal và gõ lệnh sau. Nó sẽ khởi tạo một dự án Next.js hoàn toàn mới, tích hợp sẵn TypeScript và ESLint. Với blog cá nhân, Pages Router đã đủ dùng, nên bạn cứ chọn No khi được hỏi về App Router nhé.


npx create-next-app@latest ten-blog-ngu-quyen --ts --eslint

Chờ vài giây. Khi bạn thấy thông báo Success! Created ten-blog-ngu-quyen at ... là xong. Giờ thì cd vào thư mục dự án vừa tạo:


cd ten-blog-ngu-quyen

Bước 2: Cấu hình Tailwind CSS

Tailwind đúng là một vị cứu tinh khi bạn muốn làm UI nhanh chóng mà không phải vật lộn với CSS truyền thống. Hãy cài đặt nó vào dự án của bạn bằng các lệnh sau:


npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Thực thi hai lệnh này để cài đặt Tailwind và tạo ra hai tệp cấu hình quan trọng: tailwind.config.jspostcss.config.js. Tiếp theo, bạn mở tailwind.config.js, sau đó thêm đường dẫn đến các tệp sử dụng Tailwind vào mục content:


// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

Sau đó, bạn cần bổ sung các chỉ thị của Tailwind vào tệp CSS chính của dự án. Thông thường, đó là ./styles/globals.css hoặc ./app/globals.css, tùy thuộc vào cấu trúc bạn chọn. Với Pages Router, tệp bạn cần chỉnh sửa là ./styles/globals.css:


/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Thêm CSS tùy chỉnh của bạn vào đây nếu cần */

Bước 3: Chạy thử và deploy lên Vercel

Chạy thử ứng dụng xem mọi thứ ổn chưa:


npm run dev

Hãy mở trình duyệt và truy cập http://localhost:3000. Nếu bạn thấy trang mặc định của Next.js, điều đó có nghĩa là mọi thứ đang đi đúng hướng. Giờ thì đến phần triển khai! Đây mới thực sự là ưu điểm nổi bật của Vercel – mọi thứ đơn giản đến bất ngờ.

  1. Đảm bảo dự án của bạn đã được commit lên GitHub/GitLab/Bitbucket.
  2. Truy cập Vercel Dashboard và đăng nhập (hoặc đăng ký nếu chưa có).
  3. Chọn New Project, import repository của bạn. Vercel sẽ tự động nhận diện đây là dự án Next.js và cấu hình mọi thứ.
  4. Nhấn Deploy. Trong vài phút, blog của bạn sẽ có một URL public, miễn phí!

Thấy không? Triển khai nhanh hơn cả thời gian bạn nhâm nhi ly cà phê để giữ tỉnh táo khi tìm bug nữa đấy! Giờ thì bạn có thể tạm thở phào nhẹ nhõm vì blog đã hoạt động. Tuy nhiên, đây mới chỉ là khởi đầu.

Giải thích chi tiết: Tại sao lại chọn bộ ba này?

Bình minh đã ló dạng, các lỗi cũng đã được vá. Giờ là lúc chúng ta cùng nhìn lại và phân tích sâu hơn: Vì sao bộ ba công nghệ này lại là lựa chọn lý tưởng cho một blog cá nhân, đặc biệt là khi bạn muốn tránh những đêm dài mất ngủ vì chúng?

Next.js: Không chỉ là React thuần

Next.js, dù không phải là một framework mới mẻ, nhưng lại mang đến vô vàn giá trị cho việc phát triển website, đặc biệt là với các trang giàu nội dung như blog cá nhân. Cụ thể:

  • Server-Side Rendering (SSR) & Static Site Generation (SSG): Đây là hai tính năng khiến mình ‘phải lòng’ Next.js nhất. Blog của bạn sẽ được dựng sẵn nội dung HTML ngay trên server hoặc trong quá trình build, thay vì phải chờ trình duyệt của người dùng tải và xử lý JavaScript. Điều này cải thiện rõ rệt tốc độ tải trang (performance) và tối ưu hóa SEO. Một ví dụ thực tế, một blog dùng SSG có thể tải trang trong 100-200ms, nhanh hơn đáng kể so với 500-1000ms của ứng dụng SPA thông thường. Googlebot luôn ưu tiên những trang nhanh và có sẵn nội dung để dễ dàng index hơn.

    
            // Ví dụ SSG với getStaticProps
            // pages/posts/[slug].tsx
            export async function getStaticProps(context) {
              const { slug } = context.params;
              // Lấy dữ liệu bài viết từ file Markdown hoặc API
              const post = await getPostBySlug(slug);
              return {
                props: { post },
                revalidate: 60 // Tái tạo lại trang sau mỗi 60 giây
              }
            }
    
            export async function getStaticPaths() {
              // Lấy danh sách các slug của bài viết
              const posts = await getAllPosts();
              const paths = posts.map(post => ({ params: { slug: post.slug } }));
              return {
                paths,
                fallback: false // false nếu tất cả path được generate tại build time
              }
            }
            
  • Tạo Router tự động: Bạn chỉ cần tạo tệp trong thư mục pages (hoặc app nếu sử dụng App Router), Next.js sẽ tự động tạo tuyến đường (route) tương ứng cho bạn. Tính năng này cực kỳ tiện lợi đối với một blog có nhiều bài viết.
  • API Routes: Giả sử blog cá nhân của bạn cần một số chức năng backend đơn giản, như gửi form liên hệ hay thống kê lượt xem, Next.js cho phép bạn xây dựng các API endpoint ngay trong dự án. Điều này loại bỏ nhu cầu thiết lập một server riêng biệt, giúp bạn tiết kiệm đáng kể thời gian và tài nguyên.

Tailwind CSS: Tiện ích là vua

Ban đầu, mình cũng có chút e ngại về triết lý utility-first của Tailwind CSS. Việc nhìn thấy các lớp (class) dài dằng dặc trong HTML có thể khiến bạn cảm thấy hơi ‘rối mắt’. Tuy nhiên, khi đã làm quen, rất khó để quay lại với các phương pháp truyền thống như BEM hay CSS Modules. Vậy lý do là gì?

  • Tăng tốc độ phát triển: Bạn gần như không cần phải rời khỏi tệp HTML/JSX để viết CSS. Mọi thuộc tính đều được cung cấp sẵn dưới dạng các lớp utility. Chỉ cần kết hợp các lớp như flex, items-center, justify-between, p-4, text-lg hay bg-blue-500, giao diện người dùng (UI) sẽ hoàn thành rất nhanh chóng. Điều này giúp tăng năng suất lên đến 30-50% so với việc viết CSS thủ công.
  • Tránh xung đột CSS: Mỗi lớp (class) trong Tailwind đại diện cho một thuộc tính CSS duy nhất. Nhờ vậy, bạn không còn phải lo lắng về việc thay đổi style ở một nơi lại vô tình ảnh hưởng đến các thành phần khác. Điều này giúp giảm thiểu đáng kể các lỗi vặt liên quan đến UI, đặc biệt hữu ích trong các dự án quy mô lớn. Một dự án lớn với hơn 100.000 dòng CSS có thể giảm số lượng bug UI đến 20% nhờ Tailwind.
  • Kích thước tệp cuối cùng nhỏ gọn: Với công cụ PurgeCSS được tích hợp sẵn, Tailwind chỉ đóng gói (bundle) những lớp CSS mà bạn thực sự sử dụng vào sản phẩm cuối cùng. Điều này giúp blog của bạn nhẹ hơn đáng kể, đồng thời cải thiện tốc độ tải trang. Ví dụ, một dự án trung bình có thể giảm kích thước tệp CSS từ vài trăm KB xuống còn dưới 10KB sau khi tối ưu với PurgeCSS.

Vercel: Triển khai đơn giản, hiệu năng cao

Vercel là công ty phát triển Next.js, do đó không có gì ngạc nhiên khi nền tảng này được tối ưu hóa hoàn hảo cho việc triển khai các ứng dụng Next.js. Với mình, Vercel giống như một ‘trợ thủ’ đắc lực và đáng tin cậy:

  • Triển khai tự động (Git Integration): Chỉ cần đẩy (push) mã nguồn lên kho lưu trữ Git (GitHub, GitLab, Bitbucket), Vercel sẽ tự động xây dựng và triển khai phiên bản mới của blog. Tốc độ này nhanh đến mức, có khi bạn vừa push xong, quay ra rót cốc nước đã thấy blog được deploy thành công. Đây thực sự là một điều tuyệt vời với bất kỳ nhà phát triển nào từng phải vật lộn với quy trình CI/CD thủ công. Các dự án Next.js thường được deploy chỉ trong 60-90 giây trên Vercel.
  • CDN toàn cầu: Vercel tận dụng mạng lưới phân phối nội dung (CDN) mạnh mẽ. Nhờ vậy, blog của bạn sẽ được truy cập với tốc độ tối ưu từ mọi nơi trên thế giới, bởi nội dung được phân phối từ máy chủ gần với người dùng nhất. Ví dụ, một người dùng ở Châu Âu sẽ nhận nội dung từ server gần nhất Châu Âu, thay vì phải tải từ Mỹ, giúp giảm độ trễ (latency) xuống dưới 100ms.
  • Miễn phí cho dự án cá nhân: Gói Hobby (miễn phí) của Vercel cung cấp đủ tài nguyên cho hầu hết các blog cá nhân, bao gồm 100GB băng thông và 100 build/ngày. Bạn không cần phải lo lắng về chi phí hosting khi mới bắt đầu xây dựng blog.

Nâng cao: Tối ưu và quản lý nội dung

Khi blog đã hoạt động ổn định, điều tự nhiên là chúng ta sẽ bắt đầu tìm cách cải thiện và tối ưu hóa việc quản lý nội dung. Đây là lúc cần áp dụng những kỹ thuật ‘nâng cao’ hơn một chút. Đó cũng là những bài học mà mình thực sự thấm thía giá trị khi phải refactor một codebase với 50.000 dòng mã – việc xây dựng nền tảng chuẩn ngay từ đầu là vô cùng quan trọng.

Quản lý nội dung với Markdown (MDX)

Viết blog mà cứ phải dùng HTML thì quả là một cực hình! Markdown chính là lựa chọn hoàn hảo. Để tích hợp Markdown (hoặc MDX – Markdown kết hợp với các component JSX), mình thường sử dụng các thư viện như next-mdx-remote hoặc bộ đôi remark/rehype. Phương pháp này cho phép bạn viết nội dung trong các tệp .md hoặc .mdx riêng biệt, sau đó Next.js sẽ tự động phân tích và chuyển đổi chúng thành HTML để hiển thị.


npm install gray-matter next-mdx-remote

Tạo một thư mục posts chẳng hạn, chứa các file .mdx:


---
title: Bài viết đầu tiên của tôi
date: '2023-10-26'
excerpt: Đây là một bài viết mẫu về việc xây dựng blog.
---

# Xin chào thế giới từ MDX!

Đây là nội dung bài viết của bạn. Bạn có thể viết **bold**, *italic* và sử dụng các component React:

<Button variant="primary">Đọc thêm</Button>

Rồi trong getStaticProps của trang bài viết, bạn sẽ đọc và parse file này:


// pages/posts/[slug].tsx
import { serialize } from 'next-mdx-remote/serialize';
import { MDXRemote } from 'next-mdx-remote';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const POSTS_PATH = path.join(process.cwd(), 'posts');

export default function PostPage({ source, frontMatter }) {
  return (
    <div className="container mx-auto p-4">
      <h1 className="text-4xl font-bold mb-4">{frontMatter.title}</h1>
      <p className="text-gray-500 mb-6">{frontMatter.date}</p>
      <div className="prose lg:prose-xl">
        <MDXRemote {...source} />
      </div>
    </div>
  );
}

export async function getStaticProps({ params }) {
  const postFilePath = path.join(POSTS_PATH, `${params.slug}.mdx`);
  const source = fs.readFileSync(postFilePath);

  const { content, data } = matter(source);

  const mdxSource = await serialize(content, { scope: data });

  return {
    props: {
      source: mdxSource,
      frontMatter: data,
    },
  };
}

// getStaticPaths cũng cần để tạo đường dẫn cho từng bài viết
export async function getStaticPaths() {
  const postFilePaths = fs
    .readdirSync(POSTS_PATH)
    .filter((path) => /\.mdx?$/.test(path));

  const paths = postFilePaths.map((filePath) => ({
    params: {
      slug: filePath.replace(/\.mdx?$/, ''),
    },
  }));

  return {
    paths,
    fallback: false,
  };
}

Tối ưu SEO với next/head

Có một blog nhưng không ai tìm thấy thì cũng chẳng khác gì không có. Next.js cung cấp component Head (từ next/head) giúp bạn dễ dàng quản lý các thẻ <head> trong HTML. Bao gồm tiêu đề, mô tả meta, và các thẻ Open Graph quan trọng cho mạng xã hội. Đây là yếu tố cực kỳ then chốt để tối ưu hóa công cụ tìm kiếm (SEO).


import Head from 'next/head';

export default function PostPage({ post }) {
  return (
    <>
      <Head>
        <title>{post.title} | Blog của tôi</title>
        <meta name="description" content={post.excerpt} />
        <meta property="og:title" content={post.title} />
        <meta property="og:description" content={post.excerpt} />
        <meta property="og:image" content={post.image || '/default-og-image.jpg'} />
        <meta property="og:type" content="article" />
        <!-- Thêm các meta tag khác cho Twitter Card, canonical link... -->
      </Head>
      <!-- Nội dung bài viết -->
    </>
  );
}

Tối ưu hình ảnh với next/image

Hình ảnh chất lượng cao là một phần không thể thiếu của bất kỳ blog nào, nhưng chúng cũng là tác nhân hàng đầu khiến website tải chậm. Component Image trong Next.js (thuộc thư viện next/image) sẽ tự động tối ưu hóa hình ảnh cho bạn, bao gồm:

  • Tự động resize hình ảnh theo kích thước màn hình.
  • Chuyển đổi sang các định dạng hiện đại như WebP.
  • Lazy loading (chỉ tải hình ảnh khi chúng xuất hiện trên màn hình).

import Image from 'next/image';

export default function MyComponent() {
  return (
    <Image
      src="/images/my-blog-post-image.jpg"
      alt="Mô tả hình ảnh của bài viết"
      width={800} // Kích thước gốc của hình ảnh
      height={450}
      layout="responsive" // Để hình ảnh tự động co giãn theo chiều rộng của container
      priority // Tải ngay nếu là hình ảnh chính trên màn hình đầu tiên
    />
  );
}

Test Coverage: Bài học xương máu từ production

Này, hãy lắng nghe kinh nghiệm của mình. Mình từng thực hiện refactor một codebase khổng lồ lên đến 50.000 dòng mã và bài học lớn nhất rút ra là: bạn phải có test coverage tốt ngay từ đầu. Nghe có vẻ hơi “quá mức cần thiết” cho một blog cá nhân phải không?

Nhưng đây lại là tư duy mình muốn bạn nắm vững. Ngay cả với một blog nhỏ, việc viết kiểm thử (test) cho các hàm xử lý dữ liệu (ví dụ: parse Markdown, generate RSS feed) hay các component cốt lõi sẽ giúp bạn tự tin hơn rất nhiều khi bổ sung tính năng mới hoặc thay đổi thư viện. Nó giống như một tấm bùa hộ mệnh, giúp bạn ngủ ngon giấc thay vì phải thức trắng 2 giờ sáng để vá những lỗi vặt vãnh.

Bạn có thể cân nhắc sử dụng Jest và React Testing Library để kiểm thử các component React. Hoặc, đơn giản hơn, hãy viết các unit test cơ bản cho các hàm utility (tiện ích) của mình.

Tips thực tế: Để blog của bạn ‘chất’ hơn

Khi các yếu tố cơ bản đã hoàn thiện, đây là một vài ‘mẹo’ cá nhân mình thường áp dụng để nâng tầm blog của mình, biến nó từ một trang web thông thường thành một không gian thú vị và dễ quản lý hơn.

Tùy chỉnh font chữ và icon

Giao diện website sẽ trở nên có hồn và cá tính hơn khi bạn sử dụng một bộ font chữ riêng. Google Fonts là một kho tàng khổng lồ với hàng nghìn lựa chọn miễn phí. Bạn có thể dễ dàng nhập (import) chúng vào tệp globals.css hoặc sử dụng @next/font (nếu dùng App Router của Next.js 13+). Với Pages Router, việc import thủ công vẫn là phương án phổ biến. Đối với các biểu tượng (icon), Heroicons hoặc Font Awesome luôn là những lựa chọn tuyệt vời.


/* styles/globals.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');

html, body {
  font-family: 'Inter', sans-serif;
}

Tích hợp hệ thống bình luận (Comments System)

Để blog thực sự sống động, tương tác từ độc giả là điều không thể thiếu. Mình thường tích hợp các hệ thống bình luận như Disqus hoặc Utterances (nếu bạn muốn tận dụng GitHub Issues làm backend). Việc tích hợp chúng vào Next.js rất đơn giản:


// components/Comments.tsx
import { useEffect, useRef } from 'react';

const Comments = () => {
  const commentBox = useRef(null);

  useEffect(() => {
    const scriptEl = document.createElement('script');
    scriptEl.setAttribute('src', 'https://utteranc.es/client.js');
    scriptEl.setAttribute('repo', 'YOUR_GITHUB_USERNAME/YOUR_REPO_NAME'); // Thay thế bằng repo của bạn
    scriptEl.setAttribute('issue-term', 'pathname');
    scriptEl.setAttribute('label', 'comment');
    scriptEl.setAttribute('theme', 'github-light');
    scriptEl.setAttribute('crossorigin', 'anonymous');
    scriptEl.async = true;

    commentBox.current.appendChild(scriptEl);
  }, []);

  return (
    <div className="mt-10 pt-10 border-t border-gray-200">
      <div ref={commentBox} />
    </div>
  );
};

export default Comments;

Phân tích hiệu suất với Lighthouse

Bạn hãy mở Chrome DevTools, chuyển sang tab Lighthouse và chạy quá trình kiểm tra (audit). Mặc dù Next.js và Vercel đã thực hiện rất tốt việc tối ưu hóa hiệu suất, bạn vẫn có thể tìm thấy những điểm cần cải thiện. Ví dụ điển hình là việc tối ưu hình ảnh chưa sử dụng next/image hoặc loại bỏ các tệp CSS/JS không cần thiết. Việc này có thể giúp bạn tăng điểm Lighthouse từ 90 lên 98+.

Sử dụng GitHub Actions cho CI/CD nâng cao (tùy chọn)

Vercel đã cung cấp tính năng CI/CD tự động khá tốt. Tuy nhiên, nếu bạn muốn kiểm soát nhiều hơn (như chạy kiểm thử trước khi triển khai hoặc kiểm tra code style), bạn có thể thiết lập GitHub Actions. Mặc dù bạn có thể đã đọc bài viết “Hướng dẫn CI/CD với GitHub Actions từ đầu” của mình và biết rằng nó không quá phức tạp, việc này có thể được coi là ‘over-engineering’ đối với một blog cá nhân không có yêu cầu đặc biệt. Nhưng nếu bạn muốn thực hành, đây lại là một sân chơi tuyệt vời để khám phá.

Cuối cùng, xây dựng blog cá nhân là một hành trình thú vị. Đừng quá cứng nhắc, cứ thử nghiệm, học hỏi từ những ‘bug’ mình gặp phải. Chúc bạn có một blog thật ưng ý!

Share: