第一个Next.js应用 #
一、创建首页 #
1.1 理解页面文件 #
在 Next.js 中,page.tsx 文件定义了一个路由页面:
text
src/app/page.tsx → /
src/app/about/page.tsx → /about
src/app/blog/page.tsx → /blog
1.2 修改首页 #
编辑 src/app/page.tsx:
tsx
export default function Home() {
return (
<main className="min-h-screen bg-gradient-to-b from-gray-900 to-gray-800">
<div className="container mx-auto px-4 py-16">
<h1 className="text-4xl font-bold text-white mb-4">
欢迎来到我的第一个 Next.js 应用
</h1>
<p className="text-gray-300 text-lg">
这是一个使用 Next.js 15 构建的现代化 Web 应用
</p>
</div>
</main>
)
}
1.3 页面组件特点 #
- 必须默认导出一个 React 组件
- 组件名称可以任意命名
- 返回 JSX 作为页面内容
- 可以是服务端组件(默认)
二、创建布局 #
2.1 根布局 #
编辑 src/app/layout.tsx:
tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import Navigation from '@/components/Navigation'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: '我的 Next.js 应用',
description: '使用 Next.js 15 构建的现代化应用',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN">
<body className={inter.className}>
<Navigation />
{children}
</body>
</html>
)
}
2.2 创建导航组件 #
创建 src/components/Navigation.tsx:
tsx
import Link from 'next/link'
export default function Navigation() {
return (
<nav className="bg-gray-800 text-white">
<div className="container mx-auto px-4">
<div className="flex items-center justify-between h-16">
<Link href="/" className="text-xl font-bold">
My App
</Link>
<div className="flex space-x-4">
<Link href="/" className="hover:text-gray-300">
首页
</Link>
<Link href="/about" className="hover:text-gray-300">
关于
</Link>
<Link href="/blog" className="hover:text-gray-300">
博客
</Link>
</div>
</div>
</div>
</nav>
)
}
三、创建多个页面 #
3.1 关于页面 #
创建 src/app/about/page.tsx:
tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '关于我们',
description: '了解更多关于我们的信息',
}
export default function AboutPage() {
return (
<main className="min-h-screen bg-gray-100 py-16">
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold mb-8">关于我们</h1>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-gray-600 mb-4">
这是一个使用 Next.js 构建的示例应用。
</p>
<p className="text-gray-600">
Next.js 是一个强大的 React 框架,提供了服务端渲染、
静态生成等多种渲染方式。
</p>
</div>
</div>
</main>
)
}
3.2 博客列表页 #
创建 src/app/blog/page.tsx:
tsx
import Link from 'next/link'
const posts = [
{ id: 1, title: 'Next.js 入门指南', excerpt: '学习 Next.js 的基础知识' },
{ id: 2, title: 'React Server Components', excerpt: '深入理解服务端组件' },
{ id: 3, title: 'Next.js 路由系统', excerpt: '掌握 App Router 的使用' },
]
export default function BlogPage() {
return (
<main className="min-h-screen bg-gray-100 py-16">
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold mb-8">博客文章</h1>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => (
<article
key={post.id}
className="bg-white rounded-lg shadow p-6 hover:shadow-lg transition-shadow"
>
<h2 className="text-xl font-semibold mb-2">
<Link href={`/blog/${post.id}`} className="hover:text-blue-600">
{post.title}
</Link>
</h2>
<p className="text-gray-600">{post.excerpt}</p>
</article>
))}
</div>
</div>
</main>
)
}
3.3 博客详情页 #
创建 src/app/blog/[id]/page.tsx:
tsx
interface PageProps {
params: Promise<{ id: string }>
}
const posts: Record<string, { title: string; content: string }> = {
'1': {
title: 'Next.js 入门指南',
content: 'Next.js 是一个基于 React 的全栈框架...',
},
'2': {
title: 'React Server Components',
content: '服务端组件是 React 18 引入的新特性...',
},
'3': {
title: 'Next.js 路由系统',
content: 'App Router 是 Next.js 13 引入的新路由系统...',
},
}
export async function generateMetadata({ params }: PageProps) {
const { id } = await params
const post = posts[id]
return {
title: post?.title || '文章不存在',
description: post?.content?.slice(0, 100),
}
}
export default async function BlogPostPage({ params }: PageProps) {
const { id } = await params
const post = posts[id]
if (!post) {
return (
<main className="min-h-screen bg-gray-100 py-16">
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold">文章不存在</h1>
</div>
</main>
)
}
return (
<main className="min-h-screen bg-gray-100 py-16">
<article className="container mx-auto px-4">
<h1 className="text-3xl font-bold mb-8">{post.title}</h1>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-gray-600 leading-relaxed">{post.content}</p>
</div>
</article>
</main>
)
}
四、添加样式 #
4.1 使用Tailwind CSS #
Tailwind CSS 是 Next.js 默认集成的样式方案:
tsx
export default function StyledComponent() {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-r from-purple-500 to-pink-500">
<h1 className="text-4xl font-bold text-white mb-4">
渐变背景标题
</h1>
<button className="px-6 py-3 bg-white text-purple-600 rounded-full font-semibold hover:bg-gray-100 transition-colors">
点击按钮
</button>
</div>
)
}
4.2 使用CSS模块 #
创建 src/app/about/page.module.css:
css
.container {
min-height: 100vh;
background-color: #f3f4f6;
padding: 4rem 0;
}
.card {
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
}
.title {
font-size: 1.875rem;
font-weight: 700;
margin-bottom: 1rem;
}
在组件中使用:
tsx
import styles from './page.module.css'
export default function AboutPage() {
return (
<main className={styles.container}>
<div className={styles.card}>
<h1 className={styles.title}>关于我们</h1>
<p>这是使用 CSS 模块的样式示例。</p>
</div>
</main>
)
}
4.3 全局样式 #
编辑 src/app/globals.css:
css
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--primary-color: #3b82f6;
--secondary-color: #64748b;
}
body {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
五、添加交互 #
5.1 客户端组件 #
创建 src/components/Counter.tsx:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div className="flex items-center gap-4">
<button
onClick={() => setCount(count - 1)}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
-
</button>
<span className="text-2xl font-bold">{count}</span>
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
>
+
</button>
</div>
)
}
在页面中使用:
tsx
import Counter from '@/components/Counter'
export default function InteractivePage() {
return (
<main className="min-h-screen bg-gray-100 py-16">
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold mb-8">交互示例</h1>
<Counter />
</div>
</main>
)
}
5.2 表单处理 #
创建 src/components/ContactForm.tsx:
tsx
'use client'
import { useState } from 'react'
export default function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
})
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setStatus('loading')
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
})
if (response.ok) {
setStatus('success')
setFormData({ name: '', email: '', message: '' })
} else {
setStatus('error')
}
} catch {
setStatus('error')
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-md">
<div>
<label className="block text-sm font-medium mb-1">姓名</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">邮箱</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">消息</label>
<textarea
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
className="w-full px-3 py-2 border rounded"
rows={4}
required
/>
</div>
<button
type="submit"
disabled={status === 'loading'}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{status === 'loading' ? '发送中...' : '发送'}
</button>
{status === 'success' && (
<p className="text-green-600">发送成功!</p>
)}
{status === 'error' && (
<p className="text-red-600">发送失败,请重试。</p>
)}
</form>
)
}
六、数据获取 #
6.1 服务端数据获取 #
tsx
interface User {
id: number
name: string
email: string
}
async function getUsers(): Promise<User[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error('Failed to fetch users')
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
<main className="min-h-screen bg-gray-100 py-16">
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold mb-8">用户列表</h1>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{users.map((user) => (
<div key={user.id} className="bg-white rounded-lg shadow p-4">
<h2 className="font-semibold">{user.name}</h2>
<p className="text-gray-600 text-sm">{user.email}</p>
</div>
))}
</div>
</div>
</main>
)
}
6.2 客户端数据获取 #
tsx
'use client'
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then((res) => res.json())
export default function ClientUsersPage() {
const { data, error, isLoading } = useSWR(
'https://jsonplaceholder.typicode.com/users',
fetcher
)
if (isLoading) return <div>加载中...</div>
if (error) return <div>加载失败</div>
return (
<div className="grid gap-4">
{data.map((user: any) => (
<div key={user.id} className="bg-white p-4 rounded shadow">
{user.name}
</div>
))}
</div>
)
}
七、项目运行 #
7.1 开发模式 #
bash
npm run dev
7.2 构建生产版本 #
bash
npm run build
7.3 运行生产版本 #
bash
npm run start
八、总结 #
第一个应用要点:
| 知识点 | 说明 |
|---|---|
| 页面创建 | page.tsx 文件 |
| 布局定义 | layout.tsx 文件 |
| 路由导航 | Link 组件 |
| 样式处理 | Tailwind CSS / CSS模块 |
| 客户端交互 | ‘use client’ 指令 |
| 数据获取 | async/await |
下一步,让我们深入了解 Next.js 的项目结构!
最后更新:2026-03-28