Skip to content

Commit ee2bd3f

Browse files
authored
fix duplicated noindex when server action is triggered (#76847)
### What When client sends and Server Action request with `notFound()` inside, next-server will serve the RSC payload in response but we were also serving a fallback `robots=noindex` metadata for 404 requests. This will be a conflict for users custom metadata. The fallback `robots=noindex` for 404 response was for initially for pages rendering request. Here we skip it for the dynamic RSC to avoid user's metadata being de-prioritized due to the fallback `robots=noindex` taking precedence. We also add a test that it won't break form action, where when JS is disabled and notFound involked, the new page can still be served with fallback noindex Fixes NDX-953
1 parent f121717 commit ee2bd3f

File tree

6 files changed

+68
-1
lines changed

6 files changed

+68
-1
lines changed

packages/next/src/server/app-render/app-render.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,8 @@ function NonIndex({ ctx }: { ctx: AppRenderContext }) {
427427
const isInvalidStatusCode =
428428
typeof ctx.res.statusCode === 'number' && ctx.res.statusCode > 400
429429

430-
if (is404Page || isInvalidStatusCode) {
430+
// Only render noindex for page request, skip for server actions
431+
if (!ctx.isAction && (is404Page || isInvalidStatusCode)) {
431432
return <meta name="robots" content="noindex" />
432433
}
433434
return null

test/e2e/app-dir/actions/app-action.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,15 @@ describe('app-dir action handling', () => {
359359

360360
await browser.elementByCss('#nowhere').click()
361361

362+
// Until not-found page is resolved
362363
await retry(async () => {
363364
expect(await browser.elementByCss('h1').text()).toBe('my-not-found')
364365
})
366+
367+
// Should have default noindex meta tag
368+
expect(
369+
await browser.elementByCss('meta[name="robots"]').getAttribute('content')
370+
).toBe('noindex')
365371
})
366372

367373
it('should support uploading files', async () => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use server'
2+
3+
import { notFound } from 'next/navigation'
4+
5+
export async function actionNotFound() {
6+
return notFound()
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use client'
2+
3+
import { actionNotFound } from './action'
4+
5+
export default function Page() {
6+
return (
7+
<button
8+
id="trigger-not-found"
9+
onClick={() => {
10+
actionNotFound()
11+
}}
12+
>
13+
trigger not found
14+
</button>
15+
)
16+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export { default } from './page-client'
2+
3+
export async function generateMetadata() {
4+
return {
5+
robots: 'noindex, nofollow',
6+
}
7+
}

test/e2e/app-dir/metadata-navigation/metadata-navigation.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getTitle,
66
retry,
77
} from 'next-test-utils'
8+
import { Request } from 'playwright'
89

910
describe('app dir - metadata navigation', () => {
1011
const { next } = nextTestSetup({
@@ -108,4 +109,33 @@ describe('app dir - metadata navigation', () => {
108109
expect(await browser.elementByCss('title').text()).toBe('Home Layout')
109110
})
110111
})
112+
113+
describe('server action', () => {
114+
it('should not render fallback noindex metadata if request is initiated from server action', async () => {
115+
const browser = await next.browser('/server-action/not-found')
116+
// collect server action requests
117+
let isActionSent = false
118+
browser.on('request', (req: Request) => {
119+
if (
120+
req.method() === 'POST' &&
121+
req.url().endsWith('/server-action/not-found')
122+
) {
123+
isActionSent = true
124+
}
125+
})
126+
127+
// trigger not-found action and wait until the server action is performed
128+
await browser.elementByCss('#trigger-not-found').click()
129+
await retry(async () => {
130+
expect(isActionSent).toBe(true)
131+
})
132+
133+
expect(await browser.elementsByCss('meta[name="robots"]')).toHaveLength(1)
134+
expect(
135+
await browser
136+
.elementByCss('meta[name="robots"]')
137+
.getAttribute('content')
138+
).toBe('noindex, nofollow')
139+
})
140+
})
111141
})

0 commit comments

Comments
 (0)