npx create-next-app@latest my-blog --typescript --tailwind --eslint --app --no-src-dir
cd my-blog
npm install mongoose next-themes
npm run dev
关键参数: --typescript 启用 TS,--no-src-dir 根目录布局。
Tailwind 版本检查: npm ls tailwindcss。v3 用 @tailwind base;,v4 用 @import "tailwindcss";。
✅ 验证:http://localhost:3000 看到 Next.js 欢迎页。
📁 步骤①完成后
my-blog/
├── package.json, tsconfig.json, tailwind.config.ts
├── app/ (globals.css, layout.tsx, page.tsx)
└── public/
新增: Navbar.tsx, ThemeToggle.tsx, providers.tsx | 修改: layout.tsx, tailwind.config.ts
// components/Navbar.tsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import ThemeToggle from "./ThemeToggle";
export default function Navbar() {
const pathname = usePathname();
const navItems = [{ href: "/", label: "首页", icon: "🏠" }, { href: "/posts", label: "文章", icon: "📝" }, { href: "/about", label: "关于", icon: "👤" }];
return (
<nav className="sticky top-0 z-50 backdrop-blur-lg bg-white dark:bg-gray-900 border-b">
<div className="max-w-6xl mx-auto px-6 py-4 flex justify-between items-center">
<Link href="/" className="flex items-center gap-2"><span className="text-2xl">🚀</span><span className="text-xl font-bold text-blue-700 dark:text-blue-400">My Blog</span></Link>
<div className="flex items-center gap-1">
{navItems.map((item) => (<Link key={item.href} href={item.href} className={`px-4 py-2 rounded-xl text-sm font-bold ${pathname === item.href ? "bg-gray-900 dark:bg-white text-white dark:text-black" : "hover:bg-gray-100 dark:hover:bg-gray-800"}`}>{item.icon} {item.label}</Link>))}
<div className="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-2"></div><ThemeToggle />
</div>
</div>
</nav>
);
}
// components/ThemeToggle.tsx
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export default function ThemeToggle() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return (<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")} className="p-2.5 rounded-xl bg-gray-100 dark:bg-gray-800 border hover:border-blue-500" aria-label="切换主题">{theme === "dark" ? "☀️" : "🌙"}</button>);
}
// app/providers.tsx
"use client";
import { ThemeProvider } from "next-themes";
export default function Providers({ children }: { children: React.ReactNode }) {
return (<ThemeProvider attribute="class" defaultTheme="system" enableSystem>{children}</ThemeProvider>);
}
// tailwind.config.ts - must change darkMode
import type { Config } from "tailwindcss";
const config: Config = {
content: ["./app/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}"],
darkMode: "class",
theme: { extend: {} }, plugins: [],
};
export default config;
暗色主题修正: 默认 darkMode: "media" 不响应 class,必须改 "class"。
// app/layout.tsx
import type { Metadata } from "next";
import "./globals.css";
import Navbar from "@/components/Navbar";
import Providers from "./providers";
export const metadata: Metadata = { title: "我的博客", description: "全栈博客" };
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (<html lang="zh" suppressHydrationWarning><body className="bg-gray-50 dark:bg-slate-900 text-gray-900 dark:text-gray-100 min-h-screen transition-colors"><Providers><Navbar /><main className="max-w-6xl mx-auto px-6 py-8">{children}</main></Providers></body></html>);
}
✅ 验证:导航栏可见,主题切换按钮可点击。
📁 步骤②完成后
├── app/ → globals.css, layout.tsx, page.tsx, providers.tsx
├── components/ → Navbar.tsx, ThemeToggle.tsx
└── tailwind.config.ts
新增 4 个文件(模拟数据,后续接数据库)
// app/posts/page.tsx
import Link from "next/link";
const mockPosts = [
{ slug: "hello-world", title: "Hello World", date: "2025-01-01", desc: "第一篇博文", icon: "👋" },
{ slug: "nextjs-guide", title: "Next.js 入门", date: "2025-01-15", desc: "App Router 实战", icon: "⚡" },
];
export default function PostsPage() {
return (<div><h1 className="text-3xl font-bold mb-8">📝 所有文章</h1><div className="grid gap-6">
{mockPosts.map((p) => (<Link key={p.slug} href={`/posts/${p.slug}`} className="block p-6 bg-white dark:bg-gray-800 rounded-2xl border hover:shadow-xl hover:border-blue-500 transition-all group">
<div className="flex items-start gap-4"><span className="text-4xl">{p.icon}</span><div className="flex-1"><h2 className="text-xl font-bold group-hover:text-blue-600">{p.title}</h2><p className="text-gray-500 mt-2">{p.desc}</p><p className="text-sm text-gray-400 mt-3">📅 {p.date}</p></div></div>
</Link>))}</div></div>);
}
// app/posts/[slug]/page.tsx
import CommentSection from "@/components/CommentSection";
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return (<article className="max-w-4xl mx-auto">
<header className="mb-8 pb-6 border-b border-gray-200 dark:border-gray-700">
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">{slug}</h1>
<div className="flex items-center gap-4 text-gray-500"><span>📅 2025-02-01</span><span>⏱️ 5 分钟</span></div>
</header>
<div className="prose prose-lg dark:prose-invert max-w-none mb-12"><p className="text-gray-600 dark:text-gray-300">这里是文章内容...</p></div>
<CommentSection postId={slug} />
</article>);
}
TypeScript fix: Next.js 15 params is Promise<{ slug: string }>, needs await.
// app/about/page.tsx
export default function AboutPage() {
return (<div className="max-w-4xl mx-auto">
<div className="text-center mb-12"><span className="text-7xl block mb-6">👤</span><h1 className="text-4xl font-bold bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">关于我</h1></div>
<div className="bg-white dark:bg-gray-800 rounded-2xl p-8 border shadow-lg">
<p className="text-lg text-gray-600 dark:text-gray-300 mb-6">Next.js + MongoDB 全栈博客系统。</p>
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-xl text-center">⚡ Next.js 14</div>
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-xl text-center">🗄️ MongoDB</div>
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-xl text-center">🎨 TailwindCSS</div>
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-xl text-center">🌙 暗色主题</div>
</div>
</div>
</div>);
}
// app/page.tsx
import Link from "next/link";
export default function HomePage() {
return (<div className="text-center py-20">
<span className="text-7xl animate-bounce inline-block mb-6">🚀</span>
<h1 className="text-5xl font-bold mb-4 text-black dark:text-white">我的博客</h1>
<p className="text-xl text-gray-700 dark:text-gray-300 mb-8">全栈博客 · Next.js + MongoDB</p>
<Link href="/posts" className="inline-flex items-center gap-2 bg-blue-700 text-white px-8 py-4 rounded-2xl font-bold text-lg shadow-lg hover:bg-blue-800 transition-all hover:-translate-y-1">浏览文章 →</Link>
</div>);
}
✅ 验证:/posts 看到列表,/posts/hello-world 看到详情。
📁 步骤③完成后
app/ → layout.tsx, page.tsx, providers.tsx, globals.css, posts/page.tsx, posts/[slug]/page.tsx, about/page.tsx
components/ → Navbar.tsx, ThemeToggle.tsx