Next.JS项目代码技巧

18 阅读5分钟

Wrapper交互组件

本质是Server Component 包裹 Client Component,很多时候我们可能在某个图片、某个div块上有交互事件,我们不希望整个块都变为client组件,那么我们就可以进行一个wrapper包裹

image.png

Image组件

Image组件有两种使用模式,一种是fill 模式,一种是固定尺寸的w\h模式,但是个人认为前者其实更适合大部分项目,后者限制比较大,毕竟我们要做自适应,设计给的单位又是px,fill模式是对Image对二次封装,通过relative+tailwindcss对classname实现

image.png

用法,很直观明了了

<InternalImage alt='coin' src='/game-king/icon-coin.png' className='w-[18px] h-[18px]' />

fetch+React Query结合

这里的场景是我想要保持在server端fetch数据,比如我拉取了一个为10的总数,现在我点击按钮要提交数据-1,提交之后乐观更新总数为9,从server端转client端去fetch total数据

server组件,fetch数据,将res传给client组件 image.png

client交互组件,将传入的res作为initialData传给useQuery,返回的data我们就可以做一些操作,通过mutaion去做一些乐观更新,第一次进来这里的useQuery并不会调用接口(提供了数据命中staleTime缓存),在network是看不到,不用担心重复调用 image.png

React Query的HydrationBoundary预拉取数据

Image custom Loader

我单独写了一篇文章 Next项目中静态资源的压缩、优化

template.tsx、loading.tsx

template.tsx在我业务中主要可以提供一个比较好的路由跳转的转场动画,本身路由跳转其实很生硬,会让用户意外卡了,感受比较差。除此之外,我们也要利用好Link标签和prefetch路由

组件本质就是写一个动画,我这里用了framer-motion库,本身项目使用这个也比较多 image.png

loading.tsx主要是在我们在page异步fetch的时候提供一个loading效果,当然这个效果出现的概率很少,大部分页面加载很快,一旦路由段加载完成(即页面组件渲染完毕),会自动被替换为实际页面内容,它是一个特殊的边界组件,是由 Next.js 自动管理的。

这里做了一个假的进度条的效果 image.png

RouterWrapper

这个也是Wrapper的一种,主要是针对我们用router来跳转,对各种跳转场景做了一个封装,基本覆盖了所有场景。

prefetch可以提前帮我们拉取下一个页面的rsc,跳转更快,但是最好可以做一个IntersectionObserver的判断,如果全部挂上prefetch,在pm2日志所有路由都进行请求,个人感觉这是种浪费

image.png

fetch客户端/服务端

cahce、Promise.all

cache可以让我们同一个接口在页面中多次使用,只fetch一次,类似reactQuery的命中缓存的效果

image.png

Promise.all并行请求,多个await是串行队列,比较间隔多一个空白区 image.png

dynamic

nextjs有两个dynamic,一个是针对page页面,一个是组件级别的

export const dynamic在 Next.js App Router 中,export const dynamic 是一个页面级或布局级的配置选项,用于控制rendering mode。它影响页面是静态生成force-static还是动态渲染force-dynamic,如果你开发的是官方这类的可以用force-static,大部分情况都是force-dynamic 然后你也可以在build是时候看的哪个路由是静态or动态

 'force-dynamic'

  • 页面依赖用户特定数据(如登录状态、个性化内容)。
  • 需要每次请求时获取最新数据(例如实时 API 数据、数据库查询)。
  • 页面使用动态函数如 cookies()headers()searchParams(如果需要服务器端处理)。
  • 禁用缓存或预渲染(例如用于确保排行榜数据实时更新)。

组件级别dynamic它是 next/dynamic 函数,用于动态导入(dynamic import)组件,实现代码分割(code splitting)和懒加载(lazy loading)。

  • 性能优化:当组件很大或不立即需要时,使用动态导入可以将代码分割成单独的 chunk,只在需要时加载。减少初始 bundle 大小,提升首屏加载速度。
  • 条件渲染:组件只在特定条件下显示(例如用户交互后Dialog)。
  • 避免 SSR:如果组件依赖浏览器 API(如 window),用 ssr: false 确保只在客户端渲染。(自家的AppBrigde)

cahce()、useCache、fetch的cache、revalidate和tag、浏览器的cache-control

Suspence、LazyLoadWrapper(IntersectionObserver)

API route.ts

tailwindcss w-10=>w-[10px] vscode插件开发

项目做了移动端的自适应px转vw,设计提供的单位也是px,所以项目基本是以px为开发基调,那么就会存在一个问题,tailwindcss需要对px进行包裹+px单位的书写,这在实际的开发中其实很浪费时间,每次都要去加[]px,为了解决这个问题,开发了一个vscode插件

当我们写w-10,在通过Ctrl+S保存,会自动转化为w-[10px]

tailwind-converter-px

插件本身的开发并不难,至少在这个插件上hh,本质通过正则去匹配然后转换

Typescript常用

Omit: Omit<CountdownProps, 'targetDate'>

Pick: Pick<CountdownProps, 'className' | 'textClassName' | 'wrapperClassName'>

extends: type ToArray<T> = T extends any ? T[] : never

泛型: 传T返T

export async function safeFetch<T>(fetcher: () => Promise<T>): Promise<T | null> {
  if (!isServer) {
    throw new Error("safeFetch should only be called on server")
  }

  try {
    return await fetcher()
  } catch (error) {
    console.error("[Server] API failed:", error)
    return null
  }
}

ComponentProps: ComponentProps<typeof Image>

函数重载:入参的类型、参数的个数,返回值不同

function parse(input: string): object
function parse(input: string, strict: true): object
function parse(input: string, strict: false): string
function parse(input: string, strict?: boolean) {
  if (strict === false) return input
  return JSON.parse(input)
}

const a = parse("{}", true) // object
const b = parse("{}", false) // string

gsap常用

Dialog和event-bus

middleware

vconsole

shadcn UI二次封装

error.tsx

server侧接口日志、pino pm2日志

pm2