😶‍🌫️ Next.js 踩坑记(3)——路径体操(2)

date
Jun 12, 2024
slug
path-middle2
status
Published
tags
Next.js
React
Website
summary
进一步讨论 nextjs 中静态资源的路径问题
type
Post

前言

Next.js 踩坑记(1)——路径体操 中,我们已经讨论了在 Nextjs 中,当我们部署在一个“子路径”时,如何处理静态资源路径失效的问题,今天我们进一步探讨,内容总结如下:
  1. 使用nextconfig中的publicRuntimeConfig 来定义路径,相比于使用环境变量而言更优雅;
  1. publicRuntimeConfig 只能在服务端组件中获取,但往往需要在客户端使用,如何解决该矛盾?

使用publicRuntimeConfig 定义子路径

在后续的开发过程中,我通过在nextconfig中的publicRuntimeConfig 定义一份一模一样的basePath和 assetPath,就像这样:
/** @type {import('next').NextConfig} */
const isProd = process.env.NODE_ENV === 'production'
const subBasePath = '/Promptopia'
const subAssetPrefix = subBasePath + '/'
const nextConfig = {
  basePath: isProd ? subBasePath : '',
  assetPrefix: isProd ? subAssetPrefix : '',
  publicRuntimeConfig: {
    basePath: isProd ? subBasePath : '',
    assetPrefix: isProd ? subAssetPrefix : ''
  },
  experimental: {
    serverComponentsExternalPackages: ['mongoose']
  },
  images: {
    domains: ['lh3.googleusercontent.com']
  }
}

export default nextConfig
然后这样使用:
import getConfig from 'next/config'
const { publicRuntimeConfig } = getConfig()
const { basePath } = publicRuntimeConfig
这种方式其实跟前文使用环境变量的方式是一个道理,只是你不需要再在环境变量里 copy 一份这个路径了,能够统一修改。

解决该方法只能在服务端组件中使用的问题

由于publicRuntimeConfig 只能在服务端组件中获取,但又往往需要在客户端组件中使用,我们很容易想到使用 props 传递,但当组件层级嵌套过深时,就会需要逐级传递,这是我们不需要看到的,在 React 中,我们使用 Context 组件来解决该问题。
你可能会想到,我在一个组件通过publicRuntimeConfig 来获取子路径,再通过 Context 组件提供上下文并导出,不就可以到处用了吗,这就带来我们面临的矛盾:
  • publicRuntimeConfig 只能在服务端调用,而 useContext 则正好相反,只能在客户端调用(本质上和 useState 一样,是 hook
所以“获取数据”和“提供上下文”这两件事就无法在同一层级完成,我们必须在处于更高层级的服务端组件中获取数据,通过 props 传递给当前层级的客户端组件,再在客户端组件中通过 Context 组件提供上下文给后面层级的组件,实践如下:
首先,我们需要创建一个 Context 组件:
// Context/basePathContext.js
'use client'
import { createContext } from 'react'

const basePathContext = createContext('')
export default basePathContext
然后,我们需要在服务端组件中获取数据并传递给子组件:
// app/page.jsx
import Feed from '@/components/Feed'
import React from 'react'
import getConfig from 'next/config'

const Home = () => {
  const { publicRuntimeConfig } = getConfig()
  const { basePath } = publicRuntimeConfig

  return (
    <section className="w-full flex-center flex-col">
    {//***}
      <Feed basePath={basePath} />
    {//***}
    </section>
  )
}

export default Home
在子组件中,接受数据并调用basePathContext.Provider 为更深层级提供上下文:
// components/Feed.jsx
'use client'
import basePathContext from '@/context/basePathContext'

const Feed = ({ basePath }) => {
  return (
    <basePathContext.Provider value={basePath}>
      <PromptCard/>
    </basePathContext.Provider>
  )
}

export default Feed
在后续的层级中,就可以通过useContext(basePathContext)来获取这个值
// components/PromptCard.jsx
import React, { useContext, useState } from 'react'
import basePathContext from '@/context/basePathContext'

const basePath = useContext(basePathContext)

总结

也许这个问题我们可以通过哪里需要使用路径,哪里就调用环境变量来解决,但这并不只解决了一个简单的子路径部署问题,而是解决了“仅客户端可用”和“仅服务端可用”的矛盾问题,我们通过这样的方式,可以实现在客户端使用任意的“仅服务端可用变量”。

© Musher 2019 - 2024

powered by nobelium