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

Add support for notFound in getServerSideProps (#18241)

This is a follow-up to https://github.com/vercel/next.js/pull/17755 which adds support for returning `notFound` to `getServerSideProps` also
上级 3f22490a
...@@ -772,7 +772,7 @@ const nextServerlessLoader: loader.Loader = function () { ...@@ -772,7 +772,7 @@ const nextServerlessLoader: loader.Loader = function () {
if (!renderMode) { if (!renderMode) {
if (_nextData || getStaticProps || getServerSideProps) { if (_nextData || getStaticProps || getServerSideProps) {
if (renderOpts.ssgNotFound) { if (renderOpts.isNotFound) {
res.statusCode = 404 res.statusCode = 404
const NotFoundComponent = ${ const NotFoundComponent = ${
......
...@@ -263,7 +263,7 @@ export default async function exportPage({ ...@@ -263,7 +263,7 @@ export default async function exportPage({
html = (result as any).html html = (result as any).html
} }
if (!html && !(curRenderOpts as any).ssgNotFound) { if (!html && !(curRenderOpts as any).isNotFound) {
throw new Error(`Failed to render serverless page`) throw new Error(`Failed to render serverless page`)
} }
} else { } else {
...@@ -318,7 +318,7 @@ export default async function exportPage({ ...@@ -318,7 +318,7 @@ export default async function exportPage({
html = await renderMethod(req, res, page, query, curRenderOpts) html = await renderMethod(req, res, page, query, curRenderOpts)
} }
} }
results.ssgNotFound = (curRenderOpts as any).ssgNotFound results.ssgNotFound = (curRenderOpts as any).isNotFound
const validateAmp = async ( const validateAmp = async (
rawAmpHtml: string, rawAmpHtml: string,
......
...@@ -1351,7 +1351,7 @@ export default class Server { ...@@ -1351,7 +1351,7 @@ export default class Server {
html = renderResult.html html = renderResult.html
pageData = renderResult.renderOpts.pageData pageData = renderResult.renderOpts.pageData
sprRevalidate = renderResult.renderOpts.revalidate sprRevalidate = renderResult.renderOpts.revalidate
isNotFound = renderResult.renderOpts.ssgNotFound isNotFound = renderResult.renderOpts.isNotFound
} else { } else {
const origQuery = parseUrl(req.url || '', true).query const origQuery = parseUrl(req.url || '', true).query
const resolvedUrl = formatUrl({ const resolvedUrl = formatUrl({
...@@ -1393,7 +1393,7 @@ export default class Server { ...@@ -1393,7 +1393,7 @@ export default class Server {
// TODO: change this to a different passing mechanism // TODO: change this to a different passing mechanism
pageData = (renderOpts as any).pageData pageData = (renderOpts as any).pageData
sprRevalidate = (renderOpts as any).revalidate sprRevalidate = (renderOpts as any).revalidate
isNotFound = (renderOpts as any).ssgNotFound isNotFound = (renderOpts as any).isNotFound
} }
return { html, pageData, sprRevalidate, isNotFound } return { html, pageData, sprRevalidate, isNotFound }
......
...@@ -643,7 +643,7 @@ export async function renderToHTML( ...@@ -643,7 +643,7 @@ export async function renderToHTML(
) )
} }
;(renderOpts as any).ssgNotFound = true ;(renderOpts as any).isNotFound = true
;(renderOpts as any).revalidate = false ;(renderOpts as any).revalidate = false
return null return null
} }
...@@ -753,21 +753,35 @@ export async function renderToHTML( ...@@ -753,21 +753,35 @@ export async function renderToHTML(
} }
const invalidKeys = Object.keys(data).filter( const invalidKeys = Object.keys(data).filter(
(key) => key !== 'props' && key !== 'unstable_redirect' (key) =>
key !== 'props' &&
key !== 'unstable_redirect' &&
key !== 'unstable_notFound'
) )
if (invalidKeys.length) { if (invalidKeys.length) {
throw new Error(invalidKeysMsg('getServerSideProps', invalidKeys)) throw new Error(invalidKeysMsg('getServerSideProps', invalidKeys))
} }
if ('unstable_notFound' in data) {
if (pathname === '/404') {
throw new Error(
`The /404 page can not return unstable_notFound in "getStaticProps", please remove it to continue!`
)
}
;(renderOpts as any).isNotFound = true
return null
}
if ( if (
data.unstable_redirect && 'unstable_redirect' in data &&
typeof data.unstable_redirect === 'object' typeof data.unstable_redirect === 'object'
) { ) {
checkRedirectValues(data.unstable_redirect, req) checkRedirectValues(data.unstable_redirect, req)
if (isDataReq) { if (isDataReq) {
data.props = { ;(data as any).props = {
__N_REDIRECT: data.unstable_redirect.destination, __N_REDIRECT: data.unstable_redirect.destination,
} }
} else { } else {
...@@ -778,7 +792,11 @@ export async function renderToHTML( ...@@ -778,7 +792,11 @@ export async function renderToHTML(
if ( if (
(dev || isBuildTimeSSG) && (dev || isBuildTimeSSG) &&
!isSerializableProps(pathname, 'getServerSideProps', data.props) !isSerializableProps(
pathname,
'getServerSideProps',
(data as any).props
)
) { ) {
// this fn should throw an error instead of ever returning `false` // this fn should throw an error instead of ever returning `false`
throw new Error( throw new Error(
...@@ -786,7 +804,7 @@ export async function renderToHTML( ...@@ -786,7 +804,7 @@ export async function renderToHTML(
) )
} }
props.pageProps = Object.assign({}, props.pageProps, data.props) props.pageProps = Object.assign({}, props.pageProps, (data as any).props)
;(renderOpts as any).pageData = props ;(renderOpts as any).pageData = props
} }
} catch (dataFetchError) { } catch (dataFetchError) {
......
...@@ -132,10 +132,16 @@ export type GetServerSidePropsContext< ...@@ -132,10 +132,16 @@ export type GetServerSidePropsContext<
locales?: string[] locales?: string[]
} }
export type GetServerSidePropsResult<P> = { export type GetServerSidePropsResult<P> =
props?: P | {
unstable_redirect?: Redirect props: P
} }
| {
unstable_redirect: Redirect
}
| {
unstable_notFound: true
}
export type GetServerSideProps< export type GetServerSideProps<
P extends { [key: string]: any } = { [key: string]: any }, P extends { [key: string]: any } = { [key: string]: any },
......
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function Page(props) {
const router = useRouter()
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
)
}
export const getServerSideProps = ({ query }) => {
if (query.hiding) {
return {
unstable_notFound: true,
}
}
return {
props: {
hello: 'world',
},
}
}
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function Page(props) {
const router = useRouter()
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
)
}
export const getServerSideProps = ({ query }) => {
if (query.hiding) {
return {
unstable_notFound: true,
}
}
return {
props: {
hello: 'world',
},
}
}
...@@ -118,6 +118,24 @@ const expectedManifestRoutes = () => [ ...@@ -118,6 +118,24 @@ const expectedManifestRoutes = () => [
), ),
page: '/non-json', page: '/non-json',
}, },
{
dataRouteRegex: `^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/not-found.json$`,
page: '/not-found',
},
{
dataRouteRegex: `^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/not\\-found\\/([^\\/]+?)\\.json$`,
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/not\\-found/(?<slug>[^/]+?)\\.json$`,
page: '/not-found/[slug]',
routeKeys: {
slug: 'slug',
},
},
{ {
dataRouteRegex: normalizeRegEx( dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/refresh.json$` `^\\/_next\\/data\\/${escapeRegex(buildId)}\\/refresh.json$`
...@@ -235,6 +253,50 @@ const navigateTest = (dev = false) => { ...@@ -235,6 +253,50 @@ const navigateTest = (dev = false) => {
const runTests = (dev = false) => { const runTests = (dev = false) => {
navigateTest(dev) navigateTest(dev)
it('should render 404 correctly when notFound is returned (non-dynamic)', async () => {
const res = await fetchViaHTTP(appPort, '/not-found', { hiding: true })
expect(res.status).toBe(404)
expect(await res.text()).toContain('This page could not be found')
})
it('should render 404 correctly when notFound is returned client-transition (non-dynamic)', async () => {
const browser = await webdriver(appPort, '/')
await browser.eval(`(function() {
window.beforeNav = 1
window.next.router.push('/not-found?hiding=true')
})()`)
await browser.waitForElementByCss('h1')
expect(await browser.elementByCss('html').text()).toContain(
'This page could not be found'
)
expect(await browser.eval('window.beforeNav')).toBe(null)
})
it('should render 404 correctly when notFound is returned (dynamic)', async () => {
const res = await fetchViaHTTP(appPort, '/not-found/first', {
hiding: true,
})
expect(res.status).toBe(404)
expect(await res.text()).toContain('This page could not be found')
})
it('should render 404 correctly when notFound is returned client-transition (dynamic)', async () => {
const browser = await webdriver(appPort, '/')
await browser.eval(`(function() {
window.beforeNav = 1
window.next.router.push('/not-found/first?hiding=true')
})()`)
await browser.waitForElementByCss('h1')
expect(await browser.elementByCss('html').text()).toContain(
'This page could not be found'
)
expect(await browser.eval('window.beforeNav')).toBe(null)
})
it('should SSR normal page correctly', async () => { it('should SSR normal page correctly', async () => {
const html = await renderViaHTTP(appPort, '/') const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/hello.*?world/) expect(html).toMatch(/hello.*?world/)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册