博客搭建教程(3):前端页面

我们的页面代码用的是一个叫 JSX 的语法。它长这样: {post.title} 的意思是:把 JavaScript 变量 post.title 的值插入到 HTML 里。JSX = HTML + JavaScript,可以混着写。 在 Next.js App Router 里,页面文件默认是服务端组件(Server Component)。这意味着代码在服务器上运行,直接查数据库、渲染 HTML,...

博客搭建教程(3):前端页面 — 让文章显示在网页上

React 和 JSX 是什么?

我们的页面代码用的是一个叫 JSX 的语法。它长这样:

<div>
  <h1>{post.title}</h1>
  <p>{post.content}</p>
</div>

{post.title} 的意思是:把 JavaScript 变量 post.title 的值插入到 HTML 里。JSX = HTML + JavaScript,可以混着写。

服务端组件是什么?

在 Next.js App Router 里,页面文件默认是服务端组件(Server Component)。这意味着代码在服务器上运行,直接查数据库、渲染 HTML,然后把结果发给浏览器。用户看到的网页是完整的静态 HTML,加载极快。

服务端组件的标志:文件顶部没有 "use client" 这行。

第一步:写首页

项目默认生成的 src/app/page.tsx 我们可以重写。但为了更好的组织,我们新建 src/app/(public)/page.tsx((public) 是路由组,不影响 URL):

import { prisma } from "@/lib/prisma";

export default async function HomePage() {
  // 从数据库查已发布的文章,按时间倒序
  const posts = await prisma.post.findMany({
    where: { published: true },
    orderBy: { createdAt: "desc" },
  });

  return (
    <div>
      <h1>最新文章</h1>
      {posts.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content.slice(0, 200)}...</p>
        </article>
      ))}
    </div>
  );
}

解释:

  • prisma.post.findMany() — 查多条数据
  • where: { published: true } — 只查已发布的
  • orderBy: { createdAt: "desc" } — 按创建时间倒序(新的在前)
  • posts.map(...) — 遍历每篇文章,渲染成 HTML
  • key={post.id} — React 要求列表每项有唯一 key

第二步:文章详情页

创建 src/app/(public)/articles/[slug]/page.tsx

import { prisma } from "@/lib/prisma";
import { notFound } from "next/navigation";

export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const post = await prisma.post.findUnique({
    where: { slug: params.slug },
  });

  if (!post) notFound(); // 404

  // 简单的 Markdown 渲染(把 # 变标题)
  const html = post.content
    .replace(/^### (.+)$/gm, "<h3>$1</h3>")
    .replace(/^## (.+)$/gm, "<h2>$1</h2>")
    .replace(/^# (.+)$/gm, "<h1>$1</h1>")
    .replace(/\n\n/g, "</p><p>")
    .replace(/^(.+)$/gm, "<p>$1</p>");

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </article>
  );
}

第三步:分页

文章多了需要分页。prisma.post.findMany() 接受 skip(跳过前N条)和 take(取N条):

const PAGE_SIZE = 10;
const page = 1;

const posts = await prisma.post.findMany({
  where: { published: true },
  orderBy: { createdAt: "desc" },
  skip: (page - 1) * PAGE_SIZE,
  take: PAGE_SIZE,
});

const total = await prisma.post.count({ where: { published: true } });
const totalPages = Math.ceil(total / PAGE_SIZE);

配套组件

把文章卡片、文章列表、分页按钮分别封装成组件,方便复用:

src/components/public/
├── ArticleCard.tsx     # 单篇文章卡片
├── ArticleList.tsx     # 文章列表
└── Pagination.tsx      # 分页导航

小结

这篇我们:

  1. 理解了 React/JSX/服务端组件的概念
  2. 用 Prisma 查数据库并在网页显示
  3. 实现了文章列表和文章详情页
  4. 加上了分页功能

下一篇我们添加评论系统。


💬 评论

加载中...