使用 Next.js 16、TypeScript 和 Unsplash API 构建现代图片画廊
发布: (2025年12月12日 GMT+8 04:59)
5 min read
原文: Dev.to
Source: Dev.to
🛠 技术栈与特性
核心技术
- Next.js 16 – App Router 与 Server Components
- TypeScript – 完整的类型安全
- Tailwind CSS v4 – 基于 CSS 变量的实用工具优先样式
- Unsplash API – 高质量图片来源
- Lucide React – 现代图标库
关键特性
- ✅ 首屏渲染使用服务器端渲染 (SSR)
- ✅ 热门照片使用静态站点生成 (SSG)
- ✅ 动态路由
/photos/[id] - ✅ 使用
next/image进行图片优化 - ✅ 实时搜索并使用防抖
- ✅ 分类过滤(自然、建筑等)
- ✅ 根据系统偏好检测的暗黑模式
- ✅ 响应式、移动优先设计
- ✅ SEO 优化的动态元数据
- ✅ 错误边界与加载状态
🏗 项目结构
文件目录
next-image-gallery/
├── app/
│ ├── layout.tsx # Root layout with ThemeProvider
│ ├── page.tsx # Home page (Server Component)
│ ├── loading.tsx # Global loading UI
│ ├── error.tsx # Global error boundary
│ ├── api/
│ │ └── photos/
│ │ └── route.ts # API route for client‑side fetching
│ └── photos/[id]/
│ ├── page.tsx # Dynamic photo detail page
│ ├── loading.tsx # Photo detail loading state
│ └── not-found.tsx # 404 page
├── components/
│ ├── ui/
│ │ └── Button.tsx # Reusable button component
│ ├── ImageCard.tsx # Photo card with hover effects
│ ├── ImageGrid.tsx # Responsive grid layout
│ ├── ImageDetail.tsx # Photo detail view
│ ├── Navigation.tsx # Header navigation
│ ├── ThemeToggle.tsx # Dark mode toggle (Client)
│ ├── SearchBar.tsx # Search input with debounce (Client)
│ ├── FilterBar.tsx # Category filters (Client)
│ └── GalleryClient.tsx # Client‑side orchestrator
├── lib/
│ ├── api.ts # Unsplash API functions
│ ├── types.ts # TypeScript interfaces
│ └── utils.ts # Helper functions
└── providers/
└── ThemeProvider.tsx # Theme context provider
⚡ Server Components 策略
Server 与 Client 组件
// app/page.tsx – Server Component (default)
export default async function Home() {
// ✅ Fetch data on the server
const initialPhotos = await getPhotos({
page: 1,
perPage: 20,
orderBy: "popular",
});
return <GalleryClient initialPhotos={initialPhotos} />;
}
// components/GalleryClient.tsx – Client Component
"use client"; // ← Explicit directive
export function GalleryClient({ initialPhotos }: Props) {
const [photos, setPhotos] = useState(initialPhotos);
// ...interactive logic
}
为什么重要
Server Components
- ✅ 不向客户端发送 JavaScript
- ✅ 直接访问 API/数据库
- ✅ 更好的 SEO(HTML 已包含内容)
- ✅ 更快的首屏加载
Client Components
- ✅ 交互性(hooks、事件处理)
- ✅ 可访问浏览器 API(localStorage 等)
- ✅ 实时更新
典型模式
Server Component (page.tsx)
↓
在服务器上获取数据
↓
传递给 Client Component
↓
Client Component (GalleryClient.tsx)
↓
处理用户交互
↓
通过 API 路由获取额外数据
结果: 快速的 SSR 渲染 + 丰富的客户端交互,且保持最优的 bundle 大小。
🚀 动态路由与 SSG
为热门照片生成静态页面
// app/photos/[id]/page.tsx
export async function generateStaticParams() {
const photos = await getPhotosForStaticGeneration(30);
return photos.map((photo) => ({ id: photo.id }));
}
构建时输出
npm run build
# Example output:
○ /photos/[id] (Static)
├ /photos/abc123
├ /photos/def456
├ /photos/xyz789
... (30 total pages)
动态元数据用于 SEO
export async function generateMetadata({ params }: Props): Promise<any> {
const { id } = await params;
const photo = await getPhotoById(id);
return {
title: `${photo.alt_description} - Photo Gallery`,
description: photo.description || `Photo by ${photo.user.name}`,
openGraph: {
images: [
{
url: photo.urls.regular,
width: photo.width,
height: photo.height,
},
],
},
twitter: {
card: "summary_large_image",
},
};
}
收益
- 为每张照片生成唯一的 meta 标签,实现完美 SEO
- 社交媒体预览效果美观
- 预生成集合可实现瞬时页面加载
🖼 图片优化
Next.js Image 组件
(此处应插入 next/image 的使用示例,原文此节留空。)
关键优化点
- 自动格式选择 – WebP、AVIF,回退到 JPEG/PNG
- 响应式图片 –
sizes="(max-width: 768px) 100vw, 33vw" - 懒加载 – 仅对前四张图片使用
priority={true} - 模糊占位 – 使用彩色 SVG 占位避免 CLS
配置
// next.config.ts
export default {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com",
pathname: "/**",
},
],
},
};
🔍 搜索与过滤
防抖搜索组件
// components/SearchBar.tsx
export function SearchBar({ onSearch }: Props) {
const [query, setQuery] = useState("");
useEffect(() => {
const debouncedSearch = debounce((value: string) => {
onSearch(value);
}, 500); // Wait 500 ms after user stops typing
debouncedSearch(query);
}, [query, onSearch]);
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search photos..."
/>
);
}
为什么要防抖?
如果不进行防抖,每一次键入都会触发一次网络请求,这会给 API 带来不必要的负载,并导致用户体验下降。防抖可以把快速连续的输入合并为一次请求,在用户暂停后再发送。
所有代码片段均可直接复制到 Next.js 16 项目中使用。