1:初始化
npx create-next-app@latest node 版本 21
npx create-next-app@13 node 版本 18.17.0
next12及以下老版本
默认 _app.tsx 为入口;
1.1 getStaticProps 在build阶段执行
export async function getStaticProps() {
times += 1;
console.log(`render demo1:`, times); // 刷新之后revalidate 5秒之后触发
return {
props: {
mark: 'demo1',
times,
},
revalidate: 5, // In seconds
// When a request is made to a page that was pre-rendered at build time, it will initially show the cached page
// 当发出请求让页面进行构建时,它会先返回缓存页面。
};
}
1.2 getServerSideProps 在每个请求时执行
export async function getServerSideProps() {
times += 1;
return {
props: {
times,
},
};
}
next14版本
默认 app文件下的page.tsx为入口;
middleware | 中间件 |
Next请求中间件 与src同级 配合 next-auth 做登陆验证
@folder | 命名插槽 |
|---|---|
(.)folder | 拦截同级 |
// 页面 src--app--layout.page
export default function Layout(props: { children: React.ReactNode, modal: React.ReactNode, modals: React.ReactNode }) {
return (
<html>
<body>
{/* <GithubCorner /> */}
{/* {props.modal} */}
{props.children}
{props.modals}
</body>
</html>
)
}
2:路由(档案即是路由)
最好使用 import Link from 'next/link' 进行路由跳转
OR
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false }) 方法是最好加上 e.preventDefault() 来阻止默认行为
useRouter 要配合 'use client' 使用
use client是 Next.js 提供的一个自定义钩子,用于在函数组件中获取客户端的上下文。它的作用是在服务器端渲染时避免调用客户端特定的代码,以免产生错误或不一致的行为。
举🌰:
import { redirect ,useRouter } from 'next/navigation';
const router = useRouter();
router.push('/test', { scroll: false, name: '黑' });
<Link href={{ pathname: '/test', query: { name: '黑' } }}>
import { usePathname, useSearchParams } from 'next/navigation';
const params = useSearchParams();
// URL -> `/dashboard?search=my-project` `search` -> 'my-project'
const pathname = usePathname();
console.log(params.values(), params.toString(), 'params');
(app) 文件夹路由
例如 主文件夹 app 下面有(app)文件夹 + 下一级mail 文件夹 domain.com/mail 直接访问 mail 下面的 page 页面 同时执行 provider.tsx + layout.tsx 文件
[code] 文件夹路由
例如 test 文件夹下面有 page.tsx 文件 + [code]文件夹
可以通过 domain.com/test/id 访问 [code]文件夹的页面
[code]下面的 page.tsx 参数的 context 可以拿到 { params: { code: '1' }, searchParams: {} }
app 主文件下面的 [code]
http://localhost:3000/errorurl/errorurl 会匹配 主 notFound 文件 http://localhost:3000/errorurl 会匹配 [code]下面的 page.tsx 文件
# [...code] 文件夹路由
例如 test 文件夹下面有 page.tsx 文件 + [...code]文件夹
可以通过 domain.com/test/id/id 访问 [...code]文件夹的页面
3:数据请求
3.1 fetch
const res = await fetch(`http://106.12.154.161/images/json/dummy-backend.json`, {
// cache: 'no-store',
next: { revalidate: 0, tags: [] },
//!revalidate 秒为单位
//!tags: [id], // 使用文章的ID作为标签
});
默认值:force-cache
3.1.1 重新验证:
revalidate:
fetch('https://...', { next: { revalidate: 3600 } })
revalidatePath:
'use server' import { revalidatePath } from 'next/cache'
export default async function submit() { await submitForm() revalidatePath('/')}
revalidateTag:
export default async function Page() { const res = await fetch('https://...', { next: { tags: ['collection'] } }) const data = await res.json() // ... }
'use server' import { revalidateTag } from 'next/cache' export default async function action() { revalidateTag('collection') redirect(`/post/${id}`) // 导航到新的文章页面 }
对于多个fetch 可以通过段配置选项处理(nextjs.org/docs/app/ap…)
3.1.2 第三方库: SWR(swr.vercel.app/zh-CN/docs/…)
过期就会重新验证,它有缓存,聚焦时重新验证,间隔轮询等功能。
const { data, isLoading, error } = useSWR(`http://106.12.154.161/images/json`, {
refreshInterval: 1000, //单位毫秒
onSuccess: () => {
console.log('onSuccess');
},
});
4:渲染模式
CSR(Client Side Rendering)(客户端渲染)
优点:1:可以提高网站的交互性能 2:用户在与页面交互时无需进行页面刷新
缺点:1:首次渲染,白屏时间过长 2:不利于SEO
SSG(Static Site Generation)(静态站点)
页面在构建期间只获取一次数据。静态生成页面非常快,性能良好,因为所有页面都事先构建。SSG 因此非常适合使用静态内容(比如销售页面或博客)的页面 缺点也是因为静态,不能动态渲染,每添加一篇博客,就可能需要重新构建。
ISR(Incremental Static Regeneration)(增量静态生成)
固定时间revalidate
const res = await fetch(
`http://106.12.154.161/images/json/dummy-backend.json`,
{ next: { revalidate: 20 } },
);
当访问页面时,发现 20s 没有更新页面就会重新生成新的页面,但当前访问的还是已经生成的静态页面,也就是:是否重新生成页面,需要根据上一次的生成时间来判断,并且数据会延迟 1 次。
tip:本地使用运行 yarn build 和 yarn start 来模拟生成环境,测试增量生成。`
按需revalidate增量生成
// 页面地址 app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { revalidatePath, revalidateTag } from 'next/cache';
// 手动更新页面
export async function GET(request: NextRequest) {
// 保险起见,这里可以设置一个安全校验,防止接口被非法调用
//这里的process.env.NEXT_PUBLIC_UPDATE_SSG名字要与你设置在项目中的环境变量名字相同
if (request.query.secret !== process.env.NEXT_PUBLIC_UPDATE_SSG) {
return NextResponse.json(
{ data: error, message: 'Invalid token' },
{
status: 401,
},
);
}
const path = request.nextUrl.searchParams.get('path') || '/pokemon/[name]';
// 这里可以匹配fetch请求中指定的collection变量
const collection = request.nextUrl.searchParams.get('collection') || 'collection';
// 触发更新
revalidatePath(path);
revalidateTag(collection);
return NextResponse.json({
revalidated: true,
now: Date.now(),
cache: 'no-store',
});
}
//比如我们在数据库中增加了 2 条数据,`http://localhost:3000/api/revalidate?path=/pokemon/Charmander`就可以实现`/pokemon/Charmander`这个路由的手动更新
SSR服务端渲染 (zhuanlan.zhihu.com/p/622415299)
服务器端使用 renderToString 直接渲染出的页面信息为静态 html。 客户端根据渲染出的静态 html 进行 hydrate,做一些绑定事件等操作。
目前SSR 存在的问题
当请求量增大时,每次重新渲染增加了服务器的开销。
需要等页面中所有接口请求完成才可以返回 html,虽不是白屏,但完成 hydrate 之前,页面也是不可操作
Streaming and Suspense
举个🌰:一个页面采用 SSR 的方式渲染页面,那么就需要等接口全部返回才可以看到页面,如果其中某个接口返回较慢,那么整个程序就是待响应状态。
import { SkeletonCard } from '@/ui/SkeletonCard';
import { Suspense } from 'react';
import Comments from './Comments';
export default function Posts() {
return (
<BlogList />
<section>
<BlogDetail />
<Suspense
fallback={
<div className="w-full h-40 ">
<SkeletonCard isLoading={true} />
</div>
}
>
<Comments />
</Suspense>
</section>
);
}
// Comments页面
import { use } from 'react';
async function fetchComment(): Promise<string> {
return fetch('http://www.example.com/api/comments').then((res)=>res.json())
}
export default function Comments() {
let data = use(fetchComment());
return (
<section>
{data.map((item)=><Item key={item.id}/>)}
</section>
);
}
注意事项
5:部署上架
build 项目
next build
vercel部署(vercel.com/new/yjwsurc…)
next build && next export
docker nginx 部署
nextjs.org/docs/pages/…