diff --git a/packages/next/src/server/app-render/index.tsx b/packages/next/src/server/app-render/index.tsx index 9811e8536f..4eb05c7369 100644 --- a/packages/next/src/server/app-render/index.tsx +++ b/packages/next/src/server/app-render/index.tsx @@ -1737,7 +1737,35 @@ export async function renderToHTMLOrFlight( ) let polyfillsFlushed = false - const getServerInsertedHTML = (): Promise => { + let flushedErrorMetaTagsUntilIndex = 0 + const getServerInsertedHTML = () => { + // Loop through all the errors that have been captured but not yet + // flushed. + const errorMetaTags = [] + for ( + ; + flushedErrorMetaTagsUntilIndex < allCapturedErrors.length; + flushedErrorMetaTagsUntilIndex++ + ) { + const error = allCapturedErrors[flushedErrorMetaTagsUntilIndex] + if (isNotFoundError(error)) { + errorMetaTags.push( + + ) + } else if (isRedirectError(error)) { + const redirectUrl = getURLFromRedirectError(error) + if (redirectUrl) { + errorMetaTags.push( + + ) + } + } + } + const flushed = renderToString( <> {Array.from(serverInsertedHTMLCallbacks).map((callback) => @@ -1756,6 +1784,7 @@ export async function renderToHTMLOrFlight( /> ) })} + {errorMetaTags} ) polyfillsFlushed = true diff --git a/test/e2e/app-dir/navigation/app/redirect/suspense/page.js b/test/e2e/app-dir/navigation/app/redirect/suspense/page.js new file mode 100644 index 0000000000..c3b0fa2577 --- /dev/null +++ b/test/e2e/app-dir/navigation/app/redirect/suspense/page.js @@ -0,0 +1,41 @@ +import { Suspense } from 'react' +import { redirect } from 'next/navigation' + +function createSuspenseyComponent(Component, { timeout = 0, expire = 10 }) { + let result + let promise + return function Data() { + if (result) return result + if (!promise) + promise = new Promise((resolve) => { + setTimeout(() => { + result = + setTimeout(() => { + result = undefined + promise = undefined + }, expire) + resolve() + }, timeout) + }) + throw promise + } +} + +function Redirect() { + redirect('/redirect/result') + return <> +} + +const SuspenseyRedirect = createSuspenseyComponent(Redirect, { + timeout: 300, +}) + +export default function () { + return ( +
+ + + +
+ ) +} diff --git a/test/e2e/app-dir/navigation/navigation.test.ts b/test/e2e/app-dir/navigation/navigation.test.ts index d095e3ced2..9ef5a132d5 100644 --- a/test/e2e/app-dir/navigation/navigation.test.ts +++ b/test/e2e/app-dir/navigation/navigation.test.ts @@ -105,9 +105,6 @@ createNextDescribe( ).toBe('noindex') }) it('should trigger not-found while streaming', async () => { - const initialHtml = await next.render('/not-found/suspense') - expect(initialHtml).not.toContain('noindex') - const browser = await next.browser('/not-found/suspense') expect( await browser.waitForElementByCss('#not-found-component').text() @@ -261,5 +258,19 @@ createNextDescribe( } }) }) + + describe('SEO', () => { + it('should emit noindex meta tag for not found page when streaming', async () => { + const html = await next.render('/not-found/suspense') + expect(html).toContain('') + }) + + it('should emit refresh meta tag for redirect page when streaming', async () => { + const html = await next.render('/redirect/suspense') + expect(html).toContain( + '' + ) + }) + }) } )