NextJs 一文搞定!

156 阅读5分钟

1:初始化

npx create-next-app@latest node 版本 21
npx create-next-app@13 node 版本 18.17.0

next12及以下老版本

image.png

默认 _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
    // 当发出请求让页面进行构建时,它会先返回缓存页面。
  };
}

image.png

1.2 getServerSideProps 在每个请求时执行

export async function getServerSideProps() {
  times += 1;
  return {
    props: {
      times,
    },
  };
}

image.png

next14版本

默认 app文件下的page.tsx为入口;

image.png

middleware | 中间件 |
Next请求中间件 与src同级 配合 next-auth 做登陆验证

nextjs.org/docs/app/bu…

@folder命名插槽
(.)folder拦截同级

image.png

// 页面 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]文件夹的页面

image.png

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作为标签
    });

image.png

image.png

image.png 默认值:force-cache

image.png

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('/')}

nextjs.org/docs/app/ap…

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)

image.png 服务器端使用 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>
  );
}

注意事项

image.png

5:部署上架

build 项目

next build

vercel部署(vercel.com/new/yjwsurc…)

image.png

next build && next export

docker nginx 部署
nextjs.org/docs/pages/…