Skip to content

Conversation

@Sam7
Copy link

@Sam7 Sam7 commented Apr 30, 2025

Next.js 410 Gone Status Feature

This PR adds support for HTTP 410 Gone status in Next.js, providing developers with a way to indicate that content has been permanently removed. This is an important status code for SEO and user experience, as it differentiates between content that doesn't exist (404) and content that previously existed but has been deliberately removed (410).

Features

  • New gone() function for App Router
  • Support for gone: true in getServerSideProps and getStaticProps for Pages Router
  • Custom gone.js file convention similar to not-found.js
  • Automatic noindex meta tags for Gone pages
  • Clear error messages for conflicting return values

Implementation Details

This PR implements:

  1. App Router Support:

    • New gone() function in the next/navigation module
    • New gone.js file convention for customizing the 410 error page
    • Consistent behavior with other error handling patterns
  2. Pages Router Support:

    • Added support for returning { gone: true } from data fetching functions
    • Custom /410 page support similar to /404 page
  3. API Route Support:

    • Guidance for returning 410 status from API routes

Documentation

  • Added comprehensive documentation for the feature
  • Added examples for both App Router and Pages Router implementations
  • Added error documentation for invalid combinations (e.g., gone + redirect)

Tests

The PR includes:

  • Unit tests for the gone() function
  • Integration tests for Pages Router implementation
  • E2E tests for App Router implementation
  • Tests for browser navigation behavior
  • Error validation tests

Example Usage

App Router

import { gone } from 'next/navigation'

export default function PostPage({ params }) {
  // Check if content has been deliberately removed
  if (isDeleted(params.slug)) {
    gone() // Returns 410 Gone status
  }
  
  // Continue rendering content...
}

Pages Router

export async function getServerSideProps({ params }) {
  // Check if content has been deliberately removed
  if (isDeleted(params.id)) {
    return { gone: true } // Returns 410 Gone status
  }
  
  return { props: { /* ... */ } }
}

Example Project

An example project is included in gone-status demonstrating both App Router and Pages Router implementations of the feature.

Breaking Changes

None. This is a new feature with no changes to existing behavior.

Fixes #18684
Extends #73753

@ijjk ijjk added Documentation Related to Next.js' official documentation. examples Issue was opened via the examples template. tests type: next labels Apr 30, 2025
@yanghoxom
Copy link

I hope this for next 14

@ste7en
Copy link

ste7en commented Jun 27, 2025

I hope this for next 16

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
@ijjk ijjk added the Rspack label Jul 10, 2025
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
| 'not-found'
| 'forbidden'
| 'unauthorized'
| 'gone'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid TypeScript syntax: The MetadataItems type definition uses incorrect tuple array syntax that will cause compilation errors.

📄 Review details

🔍 Technical Analysis

The MetadataItems type is defined with invalid TypeScript syntax:

export type MetadataItems = [
  Metadata | MetadataResolver | null,
  StaticMetadata,
  Viewport | ViewportResolver | null,
][]

This attempts to use a tuple type with an array suffix ([]), which is not valid TypeScript syntax. The compiler will reject this type definition.

The code appears to intend for MetadataItems to be an array of 3-element tuples, based on how it's used throughout the codebase (e.g., accessing metadataItems[i][0], metadataItems[i][1], and metadataItems[i][2]).


🔧 Suggested Fix

Change the type definition to use proper TypeScript array-of-tuples syntax:

export type MetadataItems = Array<[
  Metadata | MetadataResolver | null,
  StaticMetadata,
  Viewport | ViewportResolver | null,
]>

This correctly defines MetadataItems as an array where each element is a 3-element tuple containing metadata, static metadata, and viewport information.


👍 or 👎 to improve Vade.

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Comment on lines +36 to +56
import { gone } from 'next/navigation'

async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
if (res.status === 410) return { isDeleted: true }
if (!res.ok) return null
return res.json()
}

export default async function Post({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)

// For content that doesn't exist
if (!post) {
notFound()
}

// For content that has been deliberately removed
if (post.isDeleted) {
gone()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example is missing the notFound import which is required since it's used on line 50. Please update the import statement to include both functions:

import { gone, notFound } from 'next/navigation'

This will ensure the example works correctly when copied by developers.

Suggested change
import { gone } from 'next/navigation'
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
if (res.status === 410) return { isDeleted: true }
if (!res.ok) return null
return res.json()
}
export default async function Post({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
// For content that doesn't exist
if (!post) {
notFound()
}
// For content that has been deliberately removed
if (post.isDeleted) {
gone()
}
import { gone, notFound } from 'next/navigation'
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
if (res.status === 410) return { isDeleted: true }
if (!res.ok) return null
return res.json()
}
export default async function Post({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
// For content that doesn't exist
if (!post) {
notFound()
}
// For content that has been deliberately removed
if (post.isDeleted) {
gone()
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

if (invalidKeys.length) {
throw new Error(invalidKeysMsg('getStaticProps', invalidKeys))
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gone key is missing from the valid keys list in getStaticProps validation, which will cause { gone: true } returns to be flagged as invalid keys.

📄 Review details

🔍 Technical Analysis

In the getStaticProps validation logic, the invalidKeys filter checks for valid return keys but doesn't include 'gone' alongside 'revalidate', 'props', 'redirect', and 'notFound'. This means when a user returns { gone: true } from getStaticProps, it will be flagged as an invalid key and throw an "Additional keys were returned from getStaticProps" error before the gone logic can process it.

The current validation code:

const invalidKeys = Object.keys(data).filter(
  (key) =>
    key !== 'revalidate' &&
    key !== 'props' &&
    key !== 'redirect' &&
    key !== 'notFound'
    // Missing: && key !== 'gone'
)

This completely prevents the gone feature from working in the Pages Router with getStaticProps.


🔧 Suggested Fix

Add && key !== 'gone' to the invalidKeys filter on line 923, similar to how other valid keys are handled.


👍 or 👎 to improve Vade.


if (metadata.isGone) {
return new RenderResult(null, { metadata })
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message incorrectly mentions "getStaticProps" when it should say "getServerSideProps".

📄 Review details

🔍 Technical Analysis

In the getServerSideProps section, the error message for when /404 page returns notFound: true incorrectly references "getStaticProps" instead of "getServerSideProps". The error message reads:

The /404 page can not return notFound in "getStaticProps", please remove it to continue!

This is confusing for developers who are using getServerSideProps and will make debugging more difficult.


🔧 Suggested Fix

Change the error message to reference "getServerSideProps" instead of "getStaticProps" to match the context where this error occurs.


👍 or 👎 to improve Vade.

@adamhazon
Copy link

We are all waiting for this feature. 🙏
Any estimation if and when it will be merged and take part of next release?

@Nithur-M
Copy link

Any updates on this?

@eng-mohamed-I
Copy link

waiting 🔥🔥

@devhowyalike
Copy link

+1 waiting

@dealer-solutions-gene
Copy link

+1 Yes please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation Related to Next.js' official documentation. examples Issue was opened via the examples template. Rspack tests type: next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants