Hello World - 博客首页(SSR+ISR)

知识库
知识库文档
/tech-stacks/nextjs/examples/Hello World - 博客首页(SSR+ISR).md

文档

Next.js 博客首页 — SSR + ISR

目标

用 Next.js App Router 实现博客首页:服务端渲染文章列表,ISR 每 60 秒增量再生,同时包含客户端搜索框。

完整代码

src/app/page.tsx

import { Suspense } from 'react'
import SearchInput from './SearchInput'

// 模拟从数据库/API获取文章
async function getPosts(): Promise<Post[]> {
  // 真实场景: return fetch('https://api.example.com/posts', { next: { revalidate: 60 } })
  return [
    { id: 1, title: 'Next.js 15 新特性解读', author: '张三', date: '2024-12-01', tags: ['Next.js'] },
    { id: 2, title: 'React Server Components 深度解析', author: '李四', date: '2024-11-28', tags: ['React'] },
    { id: 3, title: '用 Prisma + PostgreSQL 构建全栈应用', author: '王五', date: '2024-11-20', tags: ['数据库'] },
  ]
}

interface Post {
  id: number
  title: string
  author: string
  date: string
  tags: string[]
}

export default async function HomePage() {
  const posts = await getPosts()

  return (
    <main className="min-h-screen bg-gray-50">
      {/* 头部 */}
      <header className="bg-white shadow-sm border-b">
        <div className="max-w-3xl mx-auto px-4 py-8">
          <h1 className="text-4xl font-extrabold text-gray-900">📝 我的博客</h1>
          <p className="text-gray-500 mt-2">ISR 每 60s 更新 · App Router · RSC</p>
        </div>
      </header>

      {/* 搜索栏(客户端组件) */}
      <Suspense fallback={<div>加载搜索框...</div>}>
        <SearchInput />
      </Suspense>

      {/* 文章列表(服务端渲染) */}
      <section className="max-w-3xl mx-auto px-4 py-8 space-y-4">
        {posts.map(post => (
          <article
            key={post.id}
            className="bg-white rounded-xl shadow-sm border p-6 hover:shadow-md transition-shadow"
          >
            <h2 className="text-xl font-semibold text-gray-900 mb-2">{post.title}</h2>
            <div className="flex items-center gap-3 text-sm text-gray-500">
              <span>✍️ {post.author}</span>
              <span>📅 {post.date}</span>
            </div>
            <div className="mt-3 flex gap-2">
              {post.tags.map(tag => (
                <span key={tag} className="bg-blue-100 text-blue-700 text-xs px-2 py-1 rounded-full">
                  {tag}
                </span>
              ))}
            </div>
          </article>
        ))}
      </section>
    </main>
  )
}

src/app/SearchInput.tsx

'use client'

import { useState } from 'react'

export default function SearchInput() {
  const [query, setQuery] = useState('')

  return (
    <div className="max-w-3xl mx-auto px-4 py-4">
      <input
        type="text"
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="🔍 搜索文章..."
        className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 text-lg"
      />
      {query && (
        <p className="mt-2 text-sm text-gray-500">
          搜索: <strong>{query}</strong> (客户端状态)
        </p>
      )}
    </div>
  )
}

运行

npm install &;& npm run dev
# → http://localhost:3000

预期输出

  • 服务端渲染的文章列表(查看页面源代码可见完整 HTML)
  • 客户端搜索框(使用 'use client' 声明)
  • 60 秒 ISR 增量再生(revalidate: 60

信息

路径
/tech-stacks/nextjs/examples/Hello World - 博客首页(SSR+ISR).md
更新时间
2026/5/30