diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index a15debacc40b1..0a22081d29d70 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -431,7 +431,6 @@ export default class Server { .replace(/\.json$/, '') .replace(/\/index$/, '/') - req.url = pathname const parsedUrl = parseUrl(pathname, true) await this.render( req, @@ -794,9 +793,14 @@ export default class Server { const url: any = req.url + // we allow custom servers to call render for all URLs + // so check if we need to serve a static _next file or not. + // we don't modify the URL for _next/data request but still + // call render so we special case this to prevent an infinite loop if ( - url.match(/^\/_next\//) || - (this.hasStaticDir && url.match(/^\/static\//)) + !query._nextDataReq && + (url.match(/^\/_next\//) || + (this.hasStaticDir && url.match(/^\/static\//))) ) { return this.handleRequest(req, res, parsedUrl) } @@ -926,17 +930,6 @@ export default class Server { const isDataReq = !!query._nextDataReq delete query._nextDataReq - // Serverless requests need its URL transformed back into the original - // request path (to emulate lambda behavior in production) - if (isLikeServerless && isDataReq) { - let { pathname } = parseUrl(req.url || '', true) - pathname = !pathname || pathname === '/' ? '/index' : pathname - req.url = formatUrl({ - pathname: `/_next/data/${this.buildId}${pathname}.json`, - query, - }) - } - let previewData: string | false | object | undefined let isPreviewMode = false @@ -1009,10 +1002,19 @@ export default class Server { return html } - // Compute the SPR cache key - const urlPathname = `${parseUrl(req.url || '').pathname!}${ + // Compute the iSSG cache key + let urlPathname = `${parseUrl(req.url || '').pathname!}${ query.amp ? '.amp' : '' }` + + // remove /_next/data prefix from urlPathname so it matches + // for direct page visit and /_next/data visit + if (isDataReq && urlPathname.includes(this.buildId)) { + urlPathname = (urlPathname.split(this.buildId).pop() || '/') + .replace(/\.json$/, '') + .replace(/\/index$/, '/') + } + const ssgCacheKey = isPreviewMode ? `__` + nanoid() // Preview mode uses a throw away key to not coalesce preview invokes : urlPathname diff --git a/test/integration/getserversideprops/pages/_app.js b/test/integration/getserversideprops/pages/_app.js new file mode 100644 index 0000000000000..07a84bbe06577 --- /dev/null +++ b/test/integration/getserversideprops/pages/_app.js @@ -0,0 +1,29 @@ +import App from 'next/app' + +class MyApp extends App { + static async getInitialProps(ctx) { + const { req, query, pathname, asPath } = ctx.ctx + let pageProps = {} + + if (ctx.Component.getInitialProps) { + pageProps = await ctx.Component.getInitialProps(ctx.ctx) + } + + return { + appProps: { + url: (req || {}).url, + query, + pathname, + asPath, + }, + pageProps, + } + } + + render() { + const { Component, pageProps, appProps } = this.props + return + } +} + +export default MyApp diff --git a/test/integration/getserversideprops/pages/blog/[post]/index.js b/test/integration/getserversideprops/pages/blog/[post]/index.js index 282d84cc1e162..6311696bad7af 100644 --- a/test/integration/getserversideprops/pages/blog/[post]/index.js +++ b/test/integration/getserversideprops/pages/blog/[post]/index.js @@ -22,13 +22,15 @@ export async function getServerSideProps({ params }) { } } -export default ({ post, time, params }) => { +export default ({ post, time, params, appProps }) => { return ( <>

Post: {post}

time: {time}
{JSON.stringify(params)}
{JSON.stringify(useRouter().query)}
+
{JSON.stringify(appProps.query)}
+
{appProps.url}
to home diff --git a/test/integration/getserversideprops/pages/something.js b/test/integration/getserversideprops/pages/something.js index 902cdcdc8c3e3..73b8374428805 100644 --- a/test/integration/getserversideprops/pages/something.js +++ b/test/integration/getserversideprops/pages/something.js @@ -14,7 +14,7 @@ export async function getServerSideProps({ params, query }) { } } -export default ({ world, time, params, random, query }) => { +export default ({ world, time, params, random, query, appProps }) => { return ( <>

hello: {world}

@@ -23,6 +23,8 @@ export default ({ world, time, params, random, query }) => {
{JSON.stringify(params)}
{JSON.stringify(query)}
{JSON.stringify(useRouter().query)}
+
{JSON.stringify(appProps.query)}
+
{appProps.url}
to home diff --git a/test/integration/getserversideprops/test/index.test.js b/test/integration/getserversideprops/test/index.test.js index cd123c5400da0..3874978278576 100644 --- a/test/integration/getserversideprops/test/index.test.js +++ b/test/integration/getserversideprops/test/index.test.js @@ -260,6 +260,46 @@ const runTests = (dev = false) => { expect(data.pageProps.params).toEqual({ path: ['first'] }) }) + it('should have original req.url for /_next/data request dynamic page', async () => { + const curUrl = `/_next/data/${buildId}/blog/post-1.json` + const data = await renderViaHTTP(appPort, curUrl) + const { appProps } = JSON.parse(data) + + expect(appProps).toEqual({ + url: curUrl, + query: { post: 'post-1' }, + asPath: curUrl, + pathname: '/blog/[post]', + }) + }) + + it('should have original req.url for /_next/data request', async () => { + const curUrl = `/_next/data/${buildId}/something.json` + const data = await renderViaHTTP(appPort, curUrl) + const { appProps } = JSON.parse(data) + + expect(appProps).toEqual({ + url: curUrl, + query: {}, + asPath: curUrl, + pathname: '/something', + }) + }) + + it('should have correct req.url and query for direct visit dynamic page', async () => { + const html = await renderViaHTTP(appPort, '/blog/post-1') + const $ = cheerio.load(html) + expect($('#app-url').text()).toContain('/blog/post-1') + expect(JSON.parse($('#app-query').text())).toEqual({ post: 'post-1' }) + }) + + it('should have correct req.url and query for direct visit', async () => { + const html = await renderViaHTTP(appPort, '/something') + const $ = cheerio.load(html) + expect($('#app-url').text()).toContain('/something') + expect(JSON.parse($('#app-query').text())).toEqual({}) + }) + it('should return data correctly', async () => { const data = JSON.parse( await renderViaHTTP(appPort, `/_next/data/${buildId}/something.json`)