Skip to content

Commit

Permalink
Add default not found template (#41750)
Browse files Browse the repository at this point in the history
It should correctly render the global not found fallback page even if
the not-found file is missing.

## Bug

- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have a helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
shuding committed Oct 24, 2022
1 parent c5111f7 commit 8fd5f0b
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 10 deletions.
76 changes: 76 additions & 0 deletions packages/next/client/components/error.tsx
@@ -0,0 +1,76 @@
import React from 'react'

const styles: { [k: string]: React.CSSProperties } = {
error: {
fontFamily:
'-apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans", Avenir, "Helvetica Neue", "Lucida Grande", sans-serif',
height: '100vh',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},

desc: {
display: 'inline-block',
textAlign: 'left',
lineHeight: '49px',
height: '49px',
verticalAlign: 'middle',
},

h1: {
display: 'inline-block',
margin: 0,
marginRight: '20px',
padding: '0 23px 0 0',
fontSize: '24px',
fontWeight: 500,
verticalAlign: 'top',
lineHeight: '49px',
},

h2: {
fontSize: '14px',
fontWeight: 'normal',
lineHeight: '49px',
margin: 0,
padding: 0,
},
}

export function NotFound() {
return (
<div style={styles.error}>
<head>
<title>404: This page could not be found.</title>
</head>
<div>
<style
dangerouslySetInnerHTML={{
__html: `
body { margin: 0; color: #000; background: #fff; }
.next-error-h1 {
border-right: 1px solid rgba(0, 0, 0, .3);
}
@media (prefers-color-scheme: dark) {
body { color: #fff; background: #000; }
.next-error-h1 {
border-right: 1px solid rgba(255, 255, 255, .3);
}
}
`,
}}
/>
<h1 className="next-error-h1" style={styles.h1}>
404
</h1>
<div style={styles.desc}>
<h2 style={styles.h2}>This page could not be found.</h2>
</div>
</div>
</div>
)
}
27 changes: 17 additions & 10 deletions packages/next/server/app-render.tsx
Expand Up @@ -7,6 +7,8 @@ import type { FontLoaderManifest } from '../build/webpack/plugins/font-loader-ma

import React, { use } from 'next/dist/compiled/react'

import { NotFound as DefaultNotFound } from '../client/components/error'

// this needs to be required lazily so that `next-server` can set
// the env before we require
import ReactDOMServer from 'next/dist/compiled/react-dom/server.browser'
Expand Down Expand Up @@ -947,7 +949,6 @@ export async function renderToHTMLOrFlight(
const Template = template
? await interopDefault(template())
: React.Fragment
const NotFound = notFound ? await interopDefault(notFound()) : undefined
const ErrorComponent = error ? await interopDefault(error()) : undefined
const Loading = loading ? await interopDefault(loading()) : undefined
const isLayout = typeof layout !== 'undefined'
Expand All @@ -957,6 +958,21 @@ export async function renderToHTMLOrFlight(
: isPage
? await page()
: undefined
/**
* Checks if the current segment is a root layout.
*/
const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded
/**
* Checks if the current segment or any level above it has a root layout.
*/
const rootLayoutIncludedAtThisLevelOrAbove =
rootLayoutIncluded || rootLayoutAtThisLevel

const NotFound = notFound
? await interopDefault(notFound())
: rootLayoutAtThisLevel
? DefaultNotFound
: undefined

if (typeof layoutOrPageMod?.revalidate !== 'undefined') {
defaultRevalidate = layoutOrPageMod.revalidate
Expand All @@ -968,15 +984,6 @@ export async function renderToHTMLOrFlight(
throw new DynamicServerError(`revalidate: 0 configured ${segment}`)
}
}
/**
* Checks if the current segment is a root layout.
*/
const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded
/**
* Checks if the current segment or any level above it has a root layout.
*/
const rootLayoutIncludedAtThisLevelOrAbove =
rootLayoutIncluded || rootLayoutAtThisLevel

// TODO-APP: move these errors to the loader instead?
// we will also need a migration doc here to link to
Expand Down
10 changes: 10 additions & 0 deletions test/e2e/app-dir/app-static.test.ts
Expand Up @@ -292,6 +292,16 @@ describe('app-dir static/dynamic handling', () => {
expect(secondDate).not.toBe(initialDate)
})

it('should render not found pages correctly and fallback to the default one', async () => {
const res = await fetchViaHTTP(next.url, `/blog/shu/hi`, undefined, {
redirect: 'manual',
})
expect(res.status).toBe(404)
const html = await res.text()
expect(html).toInclude('"noindex"')
expect(html).toInclude('This page could not be found.')
})

// TODO-APP: support fetch revalidate case for dynamic rendering
it.skip('should ssr dynamically when detected automatically with fetch revalidate option', async () => {
const pathname = '/ssr-auto/fetch-revalidate-zero'
Expand Down
6 changes: 6 additions & 0 deletions test/e2e/app-dir/app-static/app/blog/[author]/[slug]/page.js
@@ -1,6 +1,12 @@
import { notFound } from 'next/navigation'

export const dynamicParams = true

export default function Page({ params }) {
if (params.author === 'shu') {
notFound()
}

return (
<>
<p id="page">/blog/[author]/[slug]</p>
Expand Down

0 comments on commit 8fd5f0b

Please sign in to comment.