上下文:
我在Nginx后面有一个NextJS部署。想法是使用NextJS创建在不同域中托管的多个网站。每个域在Nginx中都有一个条目,并且将指向NextJS中的特定路径pages/cafes/[cafeId]
。所有网站将只有一个NextJs部署,并且每个域都将使用nginx中的static
代理进行路由。
nginx.conf
server {
listen 80;
server_name www.cafe-one.local;
location = / {
proxy_pass http://localhost:3000/cafes/cafe_id_1;
...
}
location / {
proxy_pass http://localhost:3000/;
...
}
}
server {
listen 80;
server_name www.cafe-two.local;
location = / {
proxy_pass http://localhost:3000/cafes/cafe_id_2;
...
}
location / {
proxy_pass http://localhost:3000/;
...
}
}
pages / [cafeId] /index.js
export const getStaticPaths = async () => {
return {
paths: [], // no website rendered during build time, there are around 1000+ sites
fallback: true
};
};
export const getStaticProps = async context => {
const cafeId = context.params.cafeId;
const cafe = await ... // get data from server
return {
props: {
cafe
},
revalidate: 10 // revalidate every 10 seconds
};
};
export default function CafeWebsite(props) {
const router = useRouter();
// getStaticProps() is not finished
if (router.isFallback) {
return <div>Loading...</div>;
}
return <div>{props.cafe.name}</div>;
}
问题:
当我访问www.cafe-one.local
时,我会进入加载屏幕,但是NextJS抛出关于The provided as value (/) is incompatible with the href value (/cafes/[cafeId])
的客户端错误。这是可以理解的,因为当前URL不是NextJS期望的。
问题:
如何解决该问题,以便可以在Nginx反向代理之前使用NextJS?
感谢您的帮助。
谢谢。
答案 0 :(得分:0)
我一直在处理相同的问题,只是要在NextJS应用程序中将不同的子域映射到动态路由。
我无法为The provided as value (/) is incompatible with the href value
错误找到合适的解决方案,但是我发现了一个有点棘手的解决方法。
首先,您必须将请求从my-domain.com
重定向到my-domain.com/path-to-dynamic-route
。然后,您必须将来自my-domain.com/path-to-dynamic-route
的所有请求反向代理到NextJS应用程序中的同一动态路由,例如localhost:3000/path-to-dynamic-route
。
您可以结合使用return 301
和proxy_pass
从NGINX手动进行操作,也可以通过在proxy_pass
指令中将动态路由传递给末尾来让NextJS自动执行操作斜线。
nginx.conf
server {
listen 80;
server_name www.cafe-one.local;
location = / {
# When a url to a route has a trailing slash, NextJS responds with a "308 Permanent redirect" to the path without the slash.
# In this case from /cafes/cafe_id_1/ to /cafes/cafe_id_1
proxy_pass http://localhost:3000/cafes/cafe_id_1/;
# If you also want to be able to pass parameters in the query string, you should use the variable $request_uri instead of "/"
# proxy_pass http://localhost:3000/cafes/cafe_id_1$request_uri;
...
}
location / {
# Any other request to www.cafe-one.local will keep the original route and query string
proxy_pass http://localhost:3000$request_uri;
...
}
}
这应该可以,但是现在我们地址栏中的网址有问题。任何访问www.cafe-one.local
的用户都将被重定向到www.cafe-one.local/cafes/cafe_id_1
,这看起来不太好。
我发现解决此问题的唯一方法是使用JavaScript通过用window.history.replaceState()
重写浏览历史记录来删除路径。
pages/[cafeId]/index.js
...
export default function CafeWebsite(props) {
if (typeof window !== "undefined") {
window.history.replaceState(null, "", "/")
}
...
如果您不想删除所有域的路径,则可以使用window.location.hostname
来检查当前网址。
...
export default function CafeWebsite(props) {
if (typeof window !== "undefined") {
const hostname = window.location.hostname
const regex = /^(www\.my-domain\.|my-domain\.)/
if (!regex.test(hostname)) {
window.history.replaceState(null, "", "/")
}
}
...
答案 1 :(得分:0)
感谢 Ale 的 replaceState 想法,这就是我们现在处理它的方式:
/original.host.com/
作为路径中的第一项:server {
listen 8080;
# redirect HTTP to HTTPS
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
# (not needed if you set assetPrefix in next.config.js to https://myapp.com)
location /_next {
proxy_pass https://myapp.com;
}
# a separate line for root is needed to bypass nginx messing up the request uri
location = / {
proxy_pass https://myapp.com/$host;
}
# this is the primary proxy
location / {
proxy_pass https://myapp.com/$host$request_uri;
}
}
_app.tsx
中,我们注册了一个在 Next.js 完成更改路由后运行的效果(这不会在第一次渲染时触发)。useEffect(() => {
const handleRouteChange = (url: string) => {
const paths = url.split('/')
if (paths[1] === location.host) {
// remove /original.host.com/ from the path
// note that passing history.state as the first argument makes back/forward buttons work correctly
history.replaceState(history.state, '', `/${paths.slice(2).join('/')}`)
}
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [])
所有特定于域的页面都在 pages/[domain]/
下,例如pages/[domain]/mypage.tsx
。
最后,我们在每个 href
前面加上原始主机名,例如
<Link href="/original.host.com/mypage">...</Link>
或Router.push('/original.host.com/mypage')
。不再需要使用 as
。
Next.js 现在将导航到 https://original.host.com/original.host.com/mypage
一瞬间,然后在转换完成后将其替换为 https://original.host.com/mypage
。
为了使 SSR 适用于每个特定于域的页面,我们在每个页面中添加了 getStaticPaths
/getStaticProps
,以便 Next.js 知道为每个页面生成单独版本的页面每个域(否则 router.query.domain
在 SSR 中将为空,我们会得到路径不匹配的错误)。请注意,这不会对性能产生负面影响,因为页面将在第一次请求后缓存。
// In pages/[domain]/mypage.tsx
export default function MyPage(params: MyPageProps) {
const router = useRouter()
// SSR provides domain as a param, client takes it from router.query
const domain = params.domain || router.query.domain
// ... rest of your page
}
export const getStaticPaths: GetStaticPaths = async () => ({
fallback: 'blocking',
paths: [],
})
export const getStaticProps: GetStaticProps<SpaceEditPageProps> = async ({
params,
}) => {
return {
props: {
domain: params?.domain as string,
},
}
}
passHref
,而是将假 URL 设置为 href
,取消 onClickCapture
中的默认操作,并在 onClick
中触发路由器(您可以将其提取到可重用的组件中):<Link href="/original.host.com/mypage">
<a
href="/mypage"
onClick={() => Router.push("/original.host.com/mypage")}
onClickCapture={e => e.preventDefault()}
>
// ...
</a>
</Link>