未验证 提交 67482914 编写于 作者: J JJ Kasper 提交者: GitHub

Normalize request URL/asPath for fallback SSG pages (#16352)

This interpolates the dynamic values and rebuilds the request URL for fallback SSG pages since the proxy uses the output path for non-prerendered pages on Vercel which can cause inconsistent request URL/`asPath` values for SSG pages. This also adds tests to ensure the `asPath` is correctly updated

Fixes: https://github.com/vercel/next.js/issues/16269
上级 1388aa41
......@@ -455,6 +455,29 @@ const nextServerlessLoader: loader.Loader = function () {
: ''
}
// normalize request URL/asPath for fallback pages since the proxy
// sets the request URL to the output's path for fallback pages
${
pageIsDynamicRoute
? `
if (nowParams) {
const _parsedUrl = parseUrl(req.url)
for (const param of Object.keys(defaultRouteRegex.groups)) {
const paramIdx = _parsedUrl.pathname.indexOf(\`[\${param}]\`)
if (paramIdx > -1) {
_parsedUrl.pathname = _parsedUrl.pathname.substr(0, paramIdx) +
encodeURI(nowParams[param]) +
_parsedUrl.pathname.substr(paramIdx + param.length + 2)
}
}
req.url = formatUrl(_parsedUrl)
}
`
: ``
}
const isFallback = parsedUrl.query.__nextFallback
const previewData = tryGetPreviewData(req, res, options.previewProps)
......
module.exports = {
target: 'experimental-serverless-trace',
}
import { useRouter } from 'next/router'
export default function Comment({ params }) {
const router = useRouter()
if (router.isFallback) return 'Loading...'
return (
<>
<p id="as-path">{router.asPath}</p>
<p id="query">{JSON.stringify(router.query)}</p>
<p id="params">{JSON.stringify(params)}</p>
</>
)
}
export const getStaticProps = ({ params }) => {
return {
props: {
params,
},
}
}
export const getStaticPaths = () => {
return {
paths: [],
fallback: true,
}
}
import { useRouter } from 'next/router'
export default function Post({ params }) {
const router = useRouter()
if (router.isFallback) return 'Loading...'
return (
<>
<p id="as-path">{router.asPath}</p>
<p id="query">{JSON.stringify(router.query)}</p>
<p id="params">{JSON.stringify(params)}</p>
</>
)
}
export const getStaticProps = ({ params }) => {
return {
props: {
params,
},
}
}
export const getStaticPaths = () => {
return {
paths: [],
fallback: true,
}
}
const http = require('http')
const url = require('url')
const server = http.createServer(async (req, res) => {
try {
const { pathname } = url.parse(req.url)
switch (pathname) {
case '/blog/[post]':
case '/web-app/blog/[post]':
return require('./.next/serverless/pages/blog/[post].js').render(
req,
res
)
case '/blog/[post]/[comment]':
case '/web-app/blog/[post]/[comment]':
return require('./.next/serverless/pages/blog/[post]/[comment].js').render(
req,
res
)
default:
return res.end('404')
}
} catch (err) {
console.error('error rendering', err)
}
})
server.listen(process.env.PORT, () => {
console.log('ready on', process.env.PORT)
})
/* eslint-env jest */
import fs from 'fs-extra'
import { join } from 'path'
import cheerio from 'cheerio'
import {
initNextServerScript,
killApp,
findPort,
nextBuild,
fetchViaHTTP,
} from 'next-test-utils'
jest.setTimeout(1000 * 60 * 2)
const appDir = join(__dirname, '..')
let app
let appPort
describe('Fallback asPath normalizing', () => {
beforeAll(async () => {
const startServerlessEmulator = async (dir, port, buildId) => {
const scriptPath = join(dir, 'server.js')
const env = Object.assign({}, { ...process.env }, { PORT: port })
return initNextServerScript(scriptPath, /ready on/i, env, false)
}
await fs.remove(join(appDir, '.next'))
await nextBuild(appDir)
appPort = await findPort()
app = await startServerlessEmulator(appDir, appPort)
})
afterAll(() => killApp(app))
it('should have normalized asPath for fallback page', async () => {
const html = await fetchViaHTTP(appPort, '/blog/[post]', undefined, {
headers: {
'x-now-route-matches': '1=post-1',
'x-vercel-id': 'hi',
},
}).then((res) => res.text())
const $ = cheerio.load(html)
const asPath = $('#as-path').text()
const query = JSON.parse($('#query').text())
const params = JSON.parse($('#params').text())
expect(asPath).toBe('/blog/post-1')
expect(query).toEqual({ post: 'post-1' })
expect(params).toEqual({ post: 'post-1' })
})
it('should have normalized asPath for fallback page with entry directory', async () => {
const html = await fetchViaHTTP(
appPort,
'/web-app/blog/[post]',
undefined,
{
headers: {
'x-now-route-matches': '1=post-abc',
'x-vercel-id': 'hi',
},
}
).then((res) => res.text())
const $ = cheerio.load(html)
const asPath = $('#as-path').text()
const query = JSON.parse($('#query').text())
const params = JSON.parse($('#params').text())
expect(asPath).toBe('/web-app/blog/post-abc')
expect(query).toEqual({ post: 'post-abc' })
expect(params).toEqual({ post: 'post-abc' })
})
it('should have normalized asPath for fallback page multi-params', async () => {
const html = await fetchViaHTTP(
appPort,
'/blog/[post]/[comment]',
undefined,
{
headers: {
'x-now-route-matches': '1=post-1&2=comment-2',
'x-vercel-id': 'hi',
},
}
).then((res) => res.text())
const $ = cheerio.load(html)
const asPath = $('#as-path').text()
const query = JSON.parse($('#query').text())
const params = JSON.parse($('#params').text())
expect(asPath).toBe('/blog/post-1/comment-2')
expect(query).toEqual({ post: 'post-1', comment: 'comment-2' })
expect(params).toEqual({ post: 'post-1', comment: 'comment-2' })
})
it('should have normalized asPath for fallback page with entry directory multi-params', async () => {
const html = await fetchViaHTTP(
appPort,
'/web-app/blog/[post]/[comment]',
undefined,
{
headers: {
'x-now-route-matches': '1=post-abc&2=comment-cba',
'x-vercel-id': 'hi',
},
}
).then((res) => res.text())
const $ = cheerio.load(html)
const asPath = $('#as-path').text()
const query = JSON.parse($('#query').text())
const params = JSON.parse($('#params').text())
expect(asPath).toBe('/web-app/blog/post-abc/comment-cba')
expect(query).toEqual({ post: 'post-abc', comment: 'comment-cba' })
expect(params).toEqual({ post: 'post-abc', comment: 'comment-cba' })
})
})
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册