08. 静态资源管理
Next.js 提供了强大的静态资源管理功能,包括图片优化、字体加载、public 目录等。本章将深入学习这些功能。
1. public 目录
public 目录用于存放静态资源,这些文件可以直接通过 URL 访问。
目录结构
public/
├── images/
│ ├── logo.png
│ └── hero.jpg
├── icons/
│ └── favicon.ico
└── files/
└── resume.pdf
访问方式
// 在组件中使用
<img src="/images/logo.png" alt="Logo" />
<Link href="/files/resume.pdf">下载简历</Link>⚠️ 注意:
public目录中的文件路径以/开头,不需要包含public。
2. Next.js Image 组件
Next.js 提供了优化的 Image 组件,自动处理图片优化、懒加载、响应式等。
基础用法
// src/app/page.tsx
import Image from 'next/image'
export default function Home() {
return (
<div>
<Image
src="/images/hero.jpg"
alt="Hero image"
width={800}
height={600}
/>
</div>
)
}为什么使用 Image 组件?
- ✅ 自动优化:WebP、AVIF 格式转换
- ✅ 懒加载:只加载可见区域的图片
- ✅ 响应式:根据设备自动调整尺寸
- ✅ 防止布局偏移:自动计算宽高比
重要属性
<Image
src="/images/photo.jpg"
alt="描述文字"
width={800} // 必需:图片宽度
height={600} // 必需:图片高度
priority={false} // 是否优先加载(首屏图片设为 true)
quality={75} // 图片质量(1-100,默认 75)
placeholder="blur" // 占位符类型
blurDataURL="..." // 模糊占位符数据
fill={false} // 是否填充父容器
sizes="(max-width: 768px) 100vw, 50vw" // 响应式尺寸
/>3. 实战案例:响应式图片
固定尺寸图片
// src/components/HeroImage.tsx
import Image from 'next/image'
export default function HeroImage() {
return (
<div className="relative w-full h-96">
<Image
src="/images/hero.jpg"
alt="Hero"
fill
className="object-cover"
priority
/>
</div>
)
}响应式图片
// src/components/ProductImage.tsx
import Image from 'next/image'
interface ProductImageProps {
src: string
alt: string
}
export default function ProductImage({ src, alt }: ProductImageProps) {
return (
<div className="relative w-full aspect-square">
<Image
src={src}
alt={alt}
fill
className="object-cover rounded-lg"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
)
}使用 blur 占位符
import Image from 'next/image'
const blurDataURL = 'data:image/jpeg;base64,/9j/4AAQSkZJRg...' // 小尺寸 base64 图片
export default function BlurImage() {
return (
<Image
src="/images/large.jpg"
alt="Large image"
width={1200}
height={800}
placeholder="blur"
blurDataURL={blurDataURL}
/>
)
}4. 外部图片(CDN)
配置允许的域名
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: '**.cloudinary.com', // 支持通配符
},
],
},
}使用外部图片
import Image from 'next/image'
export default function ExternalImage() {
return (
<Image
src="https://example.com/images/photo.jpg"
alt="External image"
width={800}
height={600}
/>
)
}5. 字体优化
Next.js 提供了 next/font 用于优化字体加载。
使用 Google Fonts
// src/app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN" className={inter.className}>
<body>{children}</body>
</html>
)
}使用本地字体
// src/app/layout.tsx
import localFont from 'next/font/local'
const myFont = localFont({
src: [
{
path: '../fonts/CustomFont-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: '../fonts/CustomFont-Bold.woff2',
weight: '700',
style: 'normal',
},
],
variable: '--font-custom',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN" className={myFont.variable}>
<body>{children}</body>
</html>
)
}在 CSS 中使用字体变量
/* src/app/globals.css */
:root {
--font-custom: 'CustomFont', sans-serif;
}
.custom-text {
font-family: var(--font-custom);
}6. 图标和 Favicon
使用 Next.js App Icon
在 app 目录下放置 icon.png 或 icon.ico,Next.js 会自动生成 favicon:
app/
├── icon.png # 自动生成 favicon
├── apple-icon.png # iOS 图标
└── layout.tsx
手动配置
// src/app/layout.tsx
export const metadata = {
icons: {
icon: '/icons/favicon.ico',
apple: '/icons/apple-icon.png',
},
}7. 文件下载
提供文件下载
// src/app/download/page.tsx
import Link from 'next/link'
export default function DownloadPage() {
return (
<div>
<h1>下载文件</h1>
<Link
href="/files/resume.pdf"
download
className="bg-blue-600 text-white px-6 py-3 rounded-lg"
>
下载简历
</Link>
</div>
)
}通过 API 路由提供文件
// src/app/api/download/route.ts
import { NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'
export async function GET() {
const filePath = path.join(process.cwd(), 'public', 'files', 'resume.pdf')
const fileBuffer = fs.readFileSync(filePath)
return new NextResponse(fileBuffer, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="resume.pdf"',
},
})
}8. 实战案例:图片画廊
需求
创建一个图片画廊,展示多张图片,支持响应式和懒加载。
实现
// src/app/gallery/page.tsx
import Image from 'next/image'
const images = [
{ src: '/images/gallery/1.jpg', alt: '图片 1' },
{ src: '/images/gallery/2.jpg', alt: '图片 2' },
{ src: '/images/gallery/3.jpg', alt: '图片 3' },
{ src: '/images/gallery/4.jpg', alt: '图片 4' },
{ src: '/images/gallery/5.jpg', alt: '图片 5' },
{ src: '/images/gallery/6.jpg', alt: '图片 6' },
]
export default function GalleryPage() {
return (
<div className="min-h-screen py-20">
<div className="max-w-7xl mx-auto px-4">
<h1 className="text-4xl font-bold mb-12 text-center">图片画廊</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{images.map((image, index) => (
<div
key={index}
className="relative aspect-square overflow-hidden rounded-lg shadow-md hover:shadow-xl transition-shadow"
>
<Image
src={image.src}
alt={image.alt}
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
))}
</div>
</div>
</div>
)
}9. 实战案例:头像组件
使用 Next.js Image 优化头像
// src/components/Avatar.tsx
import Image from 'next/image'
interface AvatarProps {
src: string
alt: string
size?: number
}
export default function Avatar({ src, alt, size = 64 }: AvatarProps) {
return (
<div
className="relative rounded-full overflow-hidden"
style={{ width: size, height: size }}
>
<Image
src={src}
alt={alt}
fill
className="object-cover"
sizes={`${size}px`}
/>
</div>
)
}使用示例
// src/app/profile/page.tsx
import Avatar from '@/components/Avatar'
export default function ProfilePage() {
return (
<div className="max-w-4xl mx-auto px-4 py-20">
<div className="flex items-center gap-6">
<Avatar src="/images/avatar.jpg" alt="用户头像" size={128} />
<div>
<h1 className="text-3xl font-bold">用户名</h1>
<p className="text-gray-600">用户简介</p>
</div>
</div>
</div>
)
}10. 性能优化技巧
1. 使用 priority 属性
首屏可见的图片应该设置 priority={true}:
<Image
src="/images/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // 优先加载
/>2. 使用 sizes 属性
告诉浏览器在不同屏幕尺寸下图片的显示大小:
<Image
src="/images/product.jpg"
alt="Product"
width={800}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>3. 使用 blur 占位符
提升用户体验,避免布局偏移:
<Image
src="/images/large.jpg"
alt="Large"
width={1200}
height={800}
placeholder="blur"
blurDataURL={blurDataURL}
/>11. 常见问题
问题 1:图片不显示
原因:路径错误或图片不在 public 目录
解决:
- 确保图片在
public目录下 - 使用
/images/photo.jpg而不是/public/images/photo.jpg
问题 2:外部图片报错
原因:未在 next.config.js 中配置允许的域名
解决:在 next.config.js 中添加 remotePatterns
问题 3:图片尺寸警告
原因:未指定 width 和 height,或使用 fill 时父容器未设置 position: relative
解决:
- 指定明确的宽高,或
- 使用
fill时确保父容器有relative定位
12. 实战练习:构建产品展示页
需求
创建一个产品展示页,包含:
- 产品主图(使用 Image 组件)
- 产品缩略图列表
- 响应式设计
实现
// src/app/products/[id]/page.tsx
'use client'
import { useState } from 'react'
import Image from 'next/image'
import { useParams } from 'next/navigation'
const productImages = [
'/images/products/1/main.jpg',
'/images/products/1/thumb1.jpg',
'/images/products/1/thumb2.jpg',
'/images/products/1/thumb3.jpg',
]
export default function ProductPage() {
const params = useParams()
const [selectedImage, setSelectedImage] = useState(productImages[0])
return (
<div className="max-w-6xl mx-auto px-4 py-20">
<div className="grid md:grid-cols-2 gap-8">
{/* 主图 */}
<div>
<div className="relative aspect-square mb-4">
<Image
src={selectedImage}
alt="产品主图"
fill
className="object-cover rounded-lg"
priority
/>
</div>
{/* 缩略图 */}
<div className="grid grid-cols-4 gap-2">
{productImages.map((image, index) => (
<button
key={index}
onClick={() => setSelectedImage(image)}
className={`relative aspect-square rounded overflow-hidden border-2 ${
selectedImage === image
? 'border-blue-600'
: 'border-transparent'
}`}
>
<Image
src={image}
alt={`缩略图 ${index + 1}`}
fill
className="object-cover"
/>
</button>
))}
</div>
</div>
{/* 产品信息 */}
<div>
<h1 className="text-4xl font-bold mb-4">产品名称</h1>
<p className="text-3xl font-bold text-blue-600 mb-6">¥999</p>
<p className="text-gray-700 mb-8">
这是产品的详细描述信息...
</p>
<button className="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700">
加入购物车
</button>
</div>
</div>
</div>
)
}13. 总结
本章我们学习了:
- ✅
public目录的使用 - ✅ Next.js
Image组件的优化功能 - ✅ 字体优化(
next/font) - ✅ 图标和 Favicon 配置
- ✅ 文件下载的实现
- ✅ 构建了图片画廊和产品展示页
关键要点:
- 🖼️ 始终使用
Image组件而不是<img>标签 - 🖼️ 首屏图片设置
priority={true} - 🖼️ 使用
sizes属性优化响应式图片 - 🖼️ 外部图片需要在
next.config.js中配置
下一步:恭喜!你已经完成了 Next.js 基础入门的学习。接下来可以深入学习核心机制、App Router 体系等高级内容。