😶🌫️ 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 中,当我们部署在一个“子路径”时,如何处理静态资源路径失效的问题,今天我们进一步探讨,内容总结如下:
- 使用
nextconfig
中的publicRuntimeConfig
来定义路径,相比于使用环境变量而言更优雅;
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)
总结
也许这个问题我们可以通过哪里需要使用路径,哪里就调用环境变量来解决,但这并不只解决了一个简单的子路径部署问题,而是解决了“仅客户端可用”和“仅服务端可用”的矛盾问题,我们通过这样的方式,可以实现在客户端使用任意的“仅服务端可用变量”。