Xây dựng website tĩnh với Next.js App Router, Fusionable và Markdown
Trong hướng dẫn này, chúng ta sẽ sử dụng Next.js App Router (được giới thiệu trong Next.js 13) và Fusionable để tạo một website tĩnh với nội dung dựa trên Markdown. App Router cung cấp một cách tiếp cận hiện đại để xây dựng các ứng dụng React với layouts, server components và việc lấy dữ liệu được sắp xếp hợp lý.
Chúng ta sẽ tìm hiểu các nội dung sau: thiết lập Next.js và Fusionable, tổ chức nội dung Markdown, tải và lọc nội dung với Fusionable, render Markdown với Showdown và sử dụng App Router cho các dynamic routes.
Các công cụ/tính năng chúng ta sẽ sử dụng
Để xây dựng website mẫu (và đơn giản) của chúng ta, chúng ta sẽ sử dụng các công cụ sau:
- Next.js là một framework React mạnh mẽ để xây dựng các ứng dụng web nhanh chóng và có khả năng mở rộng. Nó cung cấp các tính năng như server-side rendering (SSR), static site generation (SSG) và API routes, làm cho nó trở nên lý tưởng cho phát triển web hiện đại.
- App Router trong Next.js (được giới thiệu trong phiên bản 13) là một cách hiện đại để tổ chức ứng dụng của bạn bằng cách sử dụng React Server Components (RSC). Nó đơn giản hóa layouts, việc lấy dữ liệu và định tuyến đồng thời cải thiện hiệu suất và giảm JavaScript phía client.
- Fusionable là một thư viện JavaScript để quản lý và truy vấn nội dung. Nó hỗ trợ truy vấn dữ liệu có cấu trúc (lọc, sắp xếp và giới hạn) và hoàn hảo cho các website tĩnh với nội dung dựa trên Markdown hoặc các nguồn dữ liệu nhẹ khác. Showdown là một thư viện linh hoạt chuyển đổi Markdown thành HTML. Nó hữu ích cho việc render nội dung phong phú trên các website tĩnh bằng cách chuyển đổi các tệp Markdown văn bản thuần thành HTML được định dạng đầy đủ. Thêm thông tin về Fusionable tại: https://github.com/Hi-Folks/fusionable.
- Showdown là một thư viện đa năng chuyển đổi Markdown thành HTML. Thư viện này hữu ích cho việc hiển thị nội dung phong phú trên các trang web tĩnh bằng cách chuyển đổi các tệp Markdown dạng văn bản thuần túy thành HTML được định dạng đầy đủ.
Thiết lập dự án NEXT.js của bạn
Bắt đầu bằng cách tạo một project Next.js mới và cài đặt Fusionable và Showdown.
# Create a new Next.js app with TypeScript
bunx create-next-app@14 my-nextjs-site --typescript
# Install dependencies
cd my-nextjs-site
bun add fusionable showdown
Nếu bạn thích sử dụng npm:
# Create a new Next.js app with TypeScript
npx create-next-app@14 my-nextjs-site --typescript
# Install dependencies
cd my-nextjs-site
bun install fusionable showdown
Trong hướng dẫn này, chúng ta muốn giữ cho ứng dụng tối giản nên tôi đã sử dụng các tùy chọn sau:
- Không sử dụng ESLint
- Không sử dụng Tailwind CSS
- Sử dụng thư mục src/
- Sử dụng App Router
- Không tùy chỉnh alias import mặc định (@/*)
Tổ chức nội dung Markdown của bạn
Tạo một thư mục cho các tệp Markdown của bạn. Ví dụ:
mkdir -p content/posts
Thêm một bài đăng Markdown mẫu trong content/posts:
# content/posts/my-first-post.md
---
title: "My First Post"
date: "2023-10-01"
slug: "my-first-post"
highlight: true
---
This is an example **Markdown** post.
Mỗi bài đăng chứa siêu dữ liệu frontmatter (title, date, slug, highlight) và nội dung Markdown.
Tạo trang chủ và trang chi tiết bài viết
Trong App Router, hãy định nghĩa một component trang chủ trong src/app/page.tsx. Chúng ta sẽ triển khai hàm getPosts để tìm nạp và render tất cả các bài đăng bằng Fusionable.
// app/page.tsx
import Link from 'next/link';
import FusionCollection from 'fusionable/FusionCollection';
function getPosts() {
const collection = new FusionCollection()
.loadFromDir('content/posts')
.orderBy('date', 'desc');
return collection.getItemsArray();
}
export default function HomePage() {
const posts = getPosts(); // Static generation by default
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.fields.slug}>
<Link href={`/posts/${post.fields.slug}`}>
{post.fields.title}
</Link>
<p>{post.fields.date}</p>
</li>
))}
</ul>
</main>
);
}
Tạo trang đăng bài động
Trong App Router, các dynamic routes được xử lý bằng cách sử dụng các thư mục [slug]. Tạo cấu trúc sau:
src/
app/
posts/
[slug]/
page.tsx
Trong src/app/posts/[slug]/page.tsx, chúng ta sẽ viết mã để tải và render một bài đăng duy nhất.
// app/posts/[slug]/page.tsx
import FusionCollection from "fusionable/FusionCollection";
import { FusionFieldsType, FusionItemType } from "fusionable/FusionItem";
import Showdown from 'showdown';
function getPostBySlug(slug: string):FusionItemType {
const collection = new FusionCollection().loadFromDir('content/posts');
const post = collection.getOneBySlug(slug);
if (!post) {
throw new Error('Post not found');
}
return post.getItem();
}
export default function PostPage({ params }: { params: { slug: string } }) {
const post = getPostBySlug(params.slug); // Static generation by default
const fields: FusionFieldsType = post.fields;
const converter = new Showdown.Converter();
const contentHTML = converter.makeHtml(post.content);
return (
<>
<h1>{fields.title}</h1>
<p>{fields.date}</p>
<div dangerouslySetInnerHTML={{ __html: contentHTML }} />
</>
);
}
Chuyển đổi Markdown sang HTML với Showdown
Trong tệp src/app/posts/[slug]/page.tsx, chúng ta sử dụng Showdown để chuyển đổi nội dung Markdown thành HTML một cách linh hoạt:
import Showdown from 'showdown';
// Initialize Showdown and convert Markdown content
const converter = new Showdown.Converter();
const contentHTML = converter.makeHtml(post.content);
Kết quả contentHTML được render với dangerouslySetInnerHTML để hiển thị HTML đúng cách.
Cấu trúc thư mục đầy đủ sau khi hoàn thành
Dưới đây là cấu trúc thư mục của project của bạn sau khi thực hiện các bước trên:
my-nextjs-site/
├── src/app/
│ ├── page.tsx
│ ├── posts/
│ │ ├── [slug]/
│ │ │ ├── page.tsx
├── content/
│ ├── posts/
│ │ ├── my-first-post.md
Tổng kết
Với Next.js App Router và Fusionable, bạn đã xây dựng một website tĩnh đầy đủ chức năng với: nội dung dựa trên Markdown, dễ viết và quản lý; truy vấn Fusionable, để lọc và sắp xếp nội dung; tích hợp Showdown, để chuyển đổi Markdown thành HTML; static generation, nhanh chóng và thân thiện với SEO. Bây giờ bạn có thể mở rộng project này với các danh mục, thẻ hoặc bộ lọc bổ sung để làm cho nó năng động hơn nữa.
All rights reserved