从零到上线,我用 Claude Code 半天搭完了一个完整网站:Next.js + Cloudflare Pages 全流程复盘

一、我要做什么

2026 年辞了职,经过一段时间思考,开始全职独立开发。第一件事——给自己搭个网站。

需求很明确:写博客、展示产品、推荐工具、展示接单服务。SEO 要好,更新要方便,成本要低。

技术上定了几个方向:不用 WordPress(太重),不用第三方 CMS(不灵活),能 Markdown 写文章,能 git push 自动上线。

最终方案:Next.js 14 + Tailwind CSS + Cloudflare Pages。开发工具:Claude Code

这篇文章从零开始,完整记录从项目初始化 → 开发 → 踩坑 → 部署上线 → 日常更新的全过程。项目源码已开源,文末有 GitHub 地址。


二、第一天:用 AI 把网站主体搭起来

2.1 项目初始化

我在 Claude Code 的终端里敲了一段需求描述:

“帮我建一个 Next.js 14 个人网站,App Router + TypeScript + Tailwind CSS。全站黑白配色,点缀色用橙色。包含首页、关于、博客列表和详情、工具箱、服务页、联系页。博客用 Markdown 管理,部署到 Cloudflare Pages。”

Claude Code 大概是这么干的:

第一步:跑脚手架

npx create-next-app@14 xiaoniubuniu-web --typescript --tailwind --app

第二步:装依赖

npm install gray-matter react-markdown remark-gfm react-syntax-highlighter react-icons
npm install -D @cloudflare/next-on-pages@1.13.15

这里有个细节:@cloudflare/next-on-pages1.13.16 版本要求 next >= 14.3.0,但 Next.js 14 最新只到 14.2.35——14.3.0 这个版本号压根不存在。Claude Code 自己遍历了适配器所有历史版本,锁定了不声明 next 依赖的 1.13.15

第三步:搭建目录结构

xiaoniubuniu-web/
├── content/
│   ├── blog/          ← 博客 Markdown
│   ├── products/      ← 产品 Markdown
│   └── tools/         ← 工具箱 Markdown
├── src/
│   ├── app/           ← 页面路由
│   ├── components/    ← 全局组件
│   └── lib/           ← 数据读取 + 常量
├── public/images/     ← 图片资源
├── next.config.mjs
└── tailwind.config.ts

2.2 核心实现:Markdown 驱动的内容系统

博客、产品、工具箱三个模块全部用 Markdown 管理。以博客为例:

① 文章格式

---
title: "农村老兵的出海独立站第一周复盘"
date: "2026-06-28"
category: "build-in-public"
tags: ["出海", "独立开发", "复盘"]
description: "辞职回村第一周,我用 Next.js 搭了个个人网站,成本不到100块。"
---

# 正文内容...

② 数据读取层

// src/lib/blog.ts
import fs from "fs";
import path from "path";
import matter from "gray-matter";

export interface BlogPost {
  slug: string;
  title: string;
  date: string;
  category: string;
  tags: string[];
  description: string;
  content: string;
}

const blogDir = path.join(process.cwd(), "content/blog");

export function getAllPosts(): BlogPost[] {
  if (!fs.existsSync(blogDir)) return [];

  return fs.readdirSync(blogDir)
    .filter(f => f.endsWith(".md"))
    .map(f => {
      const slug = f.replace(".md", "");
      const raw = fs.readFileSync(path.join(blogDir, f), "utf8");
      const { data, content } = matter(raw);
      return { slug, title: data.title, date: data.date, 
               category: data.category, tags: data.tags || [], 
               description: data.description || "", content };
    })
    .sort((a, b) => (a.date < b.date ? 1 : -1));
}

③ 博客列表页(静态 + 客户端筛选)

Cloudflare Pages 要求所有页面纯静态,所以列表页在服务端生成全量 HTML,搜索、分类筛选、分页全部在客户端完成:

// src/app/blog/page.tsx — 服务端组件
import { Suspense } from "react";
import { getAllPosts } from "@/lib/blog";
import BlogFilter from "@/components/BlogFilter";

export default function BlogListPage() {
  const posts = getAllPosts(); // 构建时执行,生成全量数据
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <BlogFilter posts={posts} />
    </Suspense>
  );
}
// src/components/BlogFilter.tsx — 客户端组件
"use client";
import { useSearchParams, useRouter } from "next/navigation";

export default function BlogFilter({ posts }: { posts: BlogPost[] }) {
  const searchParams = useSearchParams();
  const router = useRouter();
  const category = searchParams.get("category") || "all";
  const search = searchParams.get("search") || "";
  
  let filtered = posts;
  if (category !== "all") filtered = filtered.filter(p => p.category === category);
  if (search) {
    const q = search.toLowerCase();
    filtered = filtered.filter(p => 
      p.title.toLowerCase().includes(q) || p.description.toLowerCase().includes(q));
  }
  
  // 分页
  const page = parseInt(searchParams.get("page") || "1");
  const perPage = 9;
  const totalPages = Math.ceil(filtered.length / perPage);
  const paged = filtered.slice((page - 1) * perPage, page * perPage);
  
  const updateParams = (key: string, value: string) => {
    const params = new URLSearchParams(searchParams.toString());
    value ? params.set(key, value) : params.delete(key);
    if (key !== "page") params.delete("page"); // 重置页码
    router.push(`/blog?${params.toString()}`, { scroll: false });
  };

  return (
    <div>
      {/* 搜索框 + 分类按钮 + 文章网格 + 分页器 */}
    </div>
  );
}

④ 博客详情页(SSG + JSON-LD)

// src/app/blog/[slug]/page.tsx
// 构建时生成所有文章的静态页面
export function generateStaticParams() {
  return getAllPostSlugs().map(slug => ({ slug }));
}

// 每篇文章独立的 SEO 元数据
export function generateMetadata({ params }): Metadata {
  const post = getPostBySlug(params.slug);
  return {
    title: post.title,
    description: post.description,
    openGraph: { type: "article", publishedTime: post.date, tags: post.tags },
  };
}

// Article 结构化数据
const jsonLd = {
  "@context": "https://schema.org",
  "@type": "Article",
  headline: post.title,
  datePublished: post.date,
  author: { "@type": "Person", name: "小牛不牛" },
};

// 正文渲染
<ReactMarkdown
  remarkPlugins={[remarkGfm]}
  components={{
    code({ className, children }) {
      const lang = /language-(\w+)/.exec(className || "")?.[1];
      return lang ? (
        <SyntaxHighlighter style={oneDark} language={lang}>
          {String(children).replace(/\n$/, "")}
        </SyntaxHighlighter>
      ) : <code className={className}>{children}</code>;
    }
  }}
>
  {post.content}
</ReactMarkdown>

2.3 整个过程花了多久

从把需求告诉 Claude Code 到浏览器里看到完整的页面,中间也不短的微调网站,大约半天。

效率提升最明显的不是"写代码"本身——而是配置类、调试类的工作。Tailwind 主题配置、TypeScript 严格模式兼容、Cloudflare 适配器版本选择、Server Component 和 Client Component 边界处理——这些事以前每一个都够折腾半小时,现在 AI 一次性全搞定。


三、踩坑和解决方案

坑 1:fs 模块不能出现在客户端

Next.js App Router 里,"use client" 组件不能直接或间接引用 fs 模块。博客、产品、工具的数据读取函数都在 lib/ 下,而分类标签等常量也在同一个文件里。如果客户端组件 import { categoryLabels } from "@/lib/products",webpack 会尝试把整个 products.ts 打进客户端 bundle,包括 import fs from "fs",直接报错。

解决方案:拆文件。

src/lib/products.ts           ← 服务端专用:fs 读取 Markdown
src/lib/product-constants.ts  ← 客户端安全:纯常量定义

客户端组件只引 product-constants,服务端组件引 products

坑 2:Cloudflare Pages 不认动态路由

博客列表页最初用服务端的 searchParams 做筛选和搜索,结果构建时报错:

The following routes were not configured to run with the Edge Runtime: /blog

原因:用了 searchParams 的页面在 Next.js 中是动态路由,Cloudflare Pages 不允许非 Edge Runtime 的动态路由。

解决方案:服务端只做 SSG 输出全量文章列表,搜索、分类切换、分页全部在客户端用 useSearchParams 完成,外层包 <Suspense>

坑 3:setupDevPlatform() 导致本地开发 404

按照 Cloudflare 文档在 next.config.mjs 中加了:

if (process.env.NODE_ENV === "development") {
  await setupDevPlatform();
}

结果 next dev 时所有 JS/CSS chunk 全部 404。

原因setupDevPlatform() 模拟了 Cloudflare Workers 的请求处理管道,拦截了 Next.js dev server 的静态文件路由。本项目是纯静态站点,根本不需要 Workers 运行时。

解决方案:删掉这段。next.config.mjs 最终只保留:

const nextConfig = {
  images: { unoptimized: true },
};
export default nextConfig;

四、部署上线:从本地到全世界

4.1 推代码到 GitHub

git init
git add .
git commit -m "feat: xiaoniubuniu.com 个人网站"
git remote add origin git@github.com:xiaoyunchengzhu/xiaoniubuniu-web.git
git push -u origin main

项目源码完全公开,欢迎 Star 和参考:

👉 GitHub: https://github.com/xiaoyunchengzhu/xiaoniubuniu-web

4.2 Cloudflare Pages 配置

登录 Cloudflare 控制台 → Workers & PagesPages连接到 Git → 选择 GitHub 仓库。

构建配置:

参数
构建命令 npx @cloudflare/next-on-pages
输出目录 .vercel/output/static
兼容性标志 nodejs_compat

点「保存并部署」,Cloudflare 自动拉代码、装依赖、构建、发布。第一次构建约 2-3 分钟。完成后得到一个 *.pages.dev 临时域名,立刻能访问。

4.3 绑定自定义域名

域名在 Namecheap 注册的 xiaoniubuniu.com。需要把 DNS 指向 Cloudflare Pages。

Namecheap 后台 → Domain List → 域名 → Advanced DNS → 添加 CNAME 记录:

Host Type Value
www CNAME xiaoniubuniu-web.pages.dev

回到 Cloudflare Pages → 项目设置 → 自定义域,添加 www.xiaoniubuniu.com。验证 DNS 生效后,SSL 证书自动签发,HTTPS 全绿。

👉 网站: https://www.xiaoniubuniu.com

4.4 完整链路

本地写 MD → git add & commit → git push → Cloudflare 自动构建 → 全球 CDN 部署

git push 到线上更新,全自动,30 秒内完成。不需要登录服务器、不拖拽文件、不重启服务。


五、日常更新:写文章只需要三步

这个站最让我满意的地方不是开发快,而是更新内容的阻力为零

发一篇新文章:

  1. content/blog/ 下新建 文章名.md,写好 frontmatter 和正文
  2. git add & git commit & git push
  3. 等 30 秒,刷新网站,文章已经在首页了

改导航栏、加新页面、调样式——同样流程。想改什么告诉 Claude Code,它改完我 review 一遍,push 上线。

之前也折腾过 WordPress 博客,最后都断更了。后来想明白了:每次发文章要登录后台、粘贴编辑器、调格式、点发布——对一个已经花了两小时写作的人来说,这些步骤的阻力足以让你放弃。Markdown + Git 这条链路,把发布从"5 步操作"变成了"1 个命令",更新不再是一件需要心理建设的事。


六、最终成本

项目 费用
域名 xiaoniubuniu.com $11.48 / 年
Cloudflare Pages 托管 免费
GitHub 代码托管 免费
Claude Code + DeepSeek V4 约 ¥5(token 消耗)

一年不到 13 美元,加上 AI 消耗折合人民币不到 100 块。

作为对比:一个 WordPress 托管方案月付 $5 起步,一年 $60。还附赠 PHP 版本升级的心跳加速体验、插件兼容性的抽盲盒式冒险,以及每月至少一次的"我的网站为什么这么慢"的灵魂拷问。


七、源码

GitHub: https://github.com/xiaoyunchengzhu/xiaoniubuniu-web

技术栈:Next.js 14 + TypeScript + Tailwind CSS + Cloudflare Pages

欢迎 Star、Fork、提 Issue。如果你也想搭一个类似的站,或者对这套方案有疑问,直接去 GitHub 提 issue 或者在博客评论区留言。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐