Skip to content

Commit

Permalink
Add support for notFound in getServerSideProps (#18241)
Browse files Browse the repository at this point in the history
This is a follow-up to #17755 which adds support for returning `notFound` to `getServerSideProps` also
  • Loading branch information
ijjk committed Oct 27, 2020
1 parent 3f22490 commit 4782bda
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 15 deletions.
Expand Up @@ -772,7 +772,7 @@ const nextServerlessLoader: loader.Loader = function () {
if (!renderMode) {
if (_nextData || getStaticProps || getServerSideProps) {
if (renderOpts.ssgNotFound) {
if (renderOpts.isNotFound) {
res.statusCode = 404
const NotFoundComponent = ${
Expand Down
4 changes: 2 additions & 2 deletions packages/next/export/worker.ts
Expand Up @@ -263,7 +263,7 @@ export default async function exportPage({
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`)
}
} else {
Expand Down Expand Up @@ -318,7 +318,7 @@ export default async function exportPage({
html = await renderMethod(req, res, page, query, curRenderOpts)
}
}
results.ssgNotFound = (curRenderOpts as any).ssgNotFound
results.ssgNotFound = (curRenderOpts as any).isNotFound

const validateAmp = async (
rawAmpHtml: string,
Expand Down
4 changes: 2 additions & 2 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -1351,7 +1351,7 @@ export default class Server {
html = renderResult.html
pageData = renderResult.renderOpts.pageData
sprRevalidate = renderResult.renderOpts.revalidate
isNotFound = renderResult.renderOpts.ssgNotFound
isNotFound = renderResult.renderOpts.isNotFound
} else {
const origQuery = parseUrl(req.url || '', true).query
const resolvedUrl = formatUrl({
Expand Down Expand Up @@ -1393,7 +1393,7 @@ export default class Server {
// TODO: change this to a different passing mechanism
pageData = (renderOpts as any).pageData
sprRevalidate = (renderOpts as any).revalidate
isNotFound = (renderOpts as any).ssgNotFound
isNotFound = (renderOpts as any).isNotFound
}

return { html, pageData, sprRevalidate, isNotFound }
Expand Down
30 changes: 24 additions & 6 deletions packages/next/next-server/server/render.tsx
Expand Up @@ -643,7 +643,7 @@ export async function renderToHTML(
)
}

;(renderOpts as any).ssgNotFound = true
;(renderOpts as any).isNotFound = true
;(renderOpts as any).revalidate = false
return null
}
Expand Down Expand Up @@ -753,21 +753,35 @@ export async function renderToHTML(
}

const invalidKeys = Object.keys(data).filter(
(key) => key !== 'props' && key !== 'unstable_redirect'
(key) =>
key !== 'props' &&
key !== 'unstable_redirect' &&
key !== 'unstable_notFound'
)

if (invalidKeys.length) {
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 (
data.unstable_redirect &&
'unstable_redirect' in data &&
typeof data.unstable_redirect === 'object'
) {
checkRedirectValues(data.unstable_redirect, req)

if (isDataReq) {
data.props = {
;(data as any).props = {
__N_REDIRECT: data.unstable_redirect.destination,
}
} else {
Expand All @@ -778,15 +792,19 @@ export async function renderToHTML(

if (
(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`
throw new Error(
'invariant: getServerSideProps did not return valid props. Please report this.'
)
}

props.pageProps = Object.assign({}, props.pageProps, data.props)
props.pageProps = Object.assign({}, props.pageProps, (data as any).props)
;(renderOpts as any).pageData = props
}
} catch (dataFetchError) {
Expand Down
14 changes: 10 additions & 4 deletions packages/next/types/index.d.ts
Expand Up @@ -132,10 +132,16 @@ export type GetServerSidePropsContext<
locales?: string[]
}

export type GetServerSidePropsResult<P> = {
props?: P
unstable_redirect?: Redirect
}
export type GetServerSidePropsResult<P> =
| {
props: P
}
| {
unstable_redirect: Redirect
}
| {
unstable_notFound: true
}

export type GetServerSideProps<
P extends { [key: string]: any } = { [key: string]: any },
Expand Down
34 changes: 34 additions & 0 deletions test/integration/getserversideprops/pages/not-found/[slug].js
@@ -0,0 +1,34 @@
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',
},
}
}
34 changes: 34 additions & 0 deletions test/integration/getserversideprops/pages/not-found/index.js
@@ -0,0 +1,34 @@
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',
},
}
}
62 changes: 62 additions & 0 deletions test/integration/getserversideprops/test/index.test.js
Expand Up @@ -118,6 +118,24 @@ const expectedManifestRoutes = () => [
),
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(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/refresh.json$`
Expand Down Expand Up @@ -235,6 +253,50 @@ const navigateTest = (dev = false) => {
const runTests = (dev = false) => {
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 () => {
const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/hello.*?world/)
Expand Down

0 comments on commit 4782bda

Please sign in to comment.