- 
                Notifications
    You must be signed in to change notification settings 
- Fork 29.7k
Feature/410 gone #78706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Open
      
      
            Sam7
  wants to merge
  6
  commits into
  vercel:canary
  
    
      
        
          
  
    
      Choose a base branch
      
     
    
      
        
      
      
        
          
          
        
        
          
            
              
              
              
  
           
        
        
          
            
              
              
           
        
       
     
  
        
          
            
          
            
          
        
       
    
      
from
Sam7:feature/410-gone
  
      
      
   
  
    
  
  
  
 
  
      
    base: canary
Could not load branches
            
              
  
    Branch not found: {{ refName }}
  
            
                
      Loading
              
            Could not load tags
            
            
              Nothing to show
            
              
  
            
                
      Loading
              
            Are you sure you want to change the base?
            Some commits from the old base branch may be removed from the timeline,
            and old review comments may become outdated.
          
          
  
     Open
                    Feature/410 gone #78706
Changes from all commits
      Commits
    
    
            Show all changes
          
          
            6 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      dfa6fcb
              
                Adding 410 Gone functionality
              
              
                ssperling-wmp 859a9c9
              
                410 Gone - Adding unit tests
              
              
                ssperling-wmp 73d417b
              
                410 Gone - Adding Examples and e2e / integration tests.
              
              
                ssperling-wmp 255f8f3
              
                Update packages/next/src/server/base-server.ts
              
              
                Sam7 7225ed7
              
                Update packages/next/src/lib/metadata/resolve-metadata.ts
              
              
                Sam7 889a4ce
              
                Update test/integration/410-page-support/pages/gone-page.js
              
              
                Sam7 File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
        
          
          
            182 changes: 182 additions & 0 deletions
          
          182 
        
  .../01-app/03-building-your-application/01-routing/09-handling-removed-content.mdx
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| --- | ||
| title: Handling Removed Content | ||
| description: Learn how to properly handle content that has been permanently removed from your Next.js application. | ||
| --- | ||
|  | ||
| # Handling Removed Content | ||
|  | ||
| There are situations where content that was once available should be marked as "permanently removed" rather than "not found." HTTP provides two different status codes for these cases: | ||
|  | ||
| - **404 Not Found**: The resource could not be found but might be available in the future | ||
| - **410 Gone**: The resource has been permanently removed and will not be available again | ||
|  | ||
| This guide explains how to implement and use the 410 Gone status in your Next.js application, which can improve your site's UX and SEO by clearly communicating when content has been permanently removed. | ||
|  | ||
| ## When to Use 410 Gone vs 404 Not Found | ||
|  | ||
| Use a **410 Gone** status when: | ||
|  | ||
| - Content has been deliberately and permanently removed | ||
| - You want search engines to remove the content from their indices faster | ||
| - You want to communicate to users that the content will not return | ||
|  | ||
| Use a **404 Not Found** status when: | ||
|  | ||
| - Content simply can't be found but might exist in the future | ||
| - A user has entered an incorrect URL | ||
| - Content is temporarily unavailable | ||
|  | ||
| ## App Router Implementation | ||
|  | ||
| ### Using the `gone()` Function in Server Components | ||
|  | ||
| In React Server Components, you can use the `gone()` function to trigger a 410 Gone response: | ||
|  | ||
| ```tsx filename="app/posts/[slug]/page.tsx" | ||
| 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() | ||
| } | ||
|  | ||
| return ( | ||
| <article> | ||
| <h1>{post.title}</h1> | ||
| <div dangerouslySetInnerHTML={{ __html: post.content }} /> | ||
| </article> | ||
| ) | ||
| } | ||
| ``` | ||
|  | ||
| ### Creating a Custom 410 Page with `gone.js` | ||
|  | ||
| You can create custom UI for 410 responses by adding a `gone.js` file to the appropriate route segment: | ||
|  | ||
| ```tsx filename="app/posts/gone.js" | ||
| export default function PostGone() { | ||
| return ( | ||
| <div className="content-gone"> | ||
| <h1>Content Permanently Removed</h1> | ||
| <p>This post has been permanently removed and is no longer available.</p> | ||
| <p> | ||
| You might be interested in <a href="/posts">our other posts</a>. | ||
| </p> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
|  | ||
| The closest `gone.js` file to the route where `gone()` was called will be used to render the UI. | ||
|  | ||
| ## Pages Router Implementation | ||
|  | ||
| ### Using 410 in Data Fetching Methods | ||
|  | ||
| For the Pages Router, you can return `{ gone: true }` from data fetching methods to trigger a 410 Gone response: | ||
|  | ||
| ```tsx filename="pages/posts/[slug].tsx" | ||
| export async function getServerSideProps({ params }) { | ||
| const res = await fetch(`https://api.example.com/posts/${params.slug}`) | ||
|  | ||
| if (res.status === 410) { | ||
| // Return gone: true to indicate the content has been permanently removed | ||
| return { | ||
| gone: true, | ||
| } | ||
| } | ||
|  | ||
| if (!res.ok) { | ||
| // Return notFound for content that couldn't be found | ||
| return { | ||
| notFound: true, | ||
| } | ||
| } | ||
|  | ||
| const post = await res.json() | ||
|  | ||
| return { | ||
| props: { | ||
| post, | ||
| }, | ||
| } | ||
| } | ||
|  | ||
| export default function Post({ post }) { | ||
| return ( | ||
| <article> | ||
| <h1>{post.title}</h1> | ||
| <div dangerouslySetInnerHTML={{ __html: post.content }} /> | ||
| </article> | ||
| ) | ||
| } | ||
| ``` | ||
|  | ||
| ### Creating a Custom 410 Page | ||
|  | ||
| For the Pages Router, you can create a custom 410 page by adding a `410.js` file to your `pages` directory: | ||
|  | ||
| ```tsx filename="pages/410.js" | ||
| export default function Custom410() { | ||
| return ( | ||
| <div className="error-container"> | ||
| <h1>410 - Content Permanently Removed</h1> | ||
| <p> | ||
| The content you are looking for has been permanently removed and is no | ||
| longer available. | ||
| </p> | ||
| <p> | ||
| <a href="/">Return to home page</a> | ||
| </p> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
|  | ||
| ## SEO Benefits of Using 410 Gone | ||
|  | ||
| Using the 410 status code provides several SEO benefits: | ||
|  | ||
| 1. **Faster removal from search indices**: Search engines like Google treat 410 responses as a strong signal that the content should be removed from their indices more quickly than with a 404. | ||
|  | ||
| 2. **Clear communication**: It clearly indicates that the removal was intentional rather than a temporary error or missing content. | ||
|  | ||
| 3. **User experience**: It allows you to create specific messaging for users looking for content that has been deliberately removed. | ||
|  | ||
| All `gone.js` components and pages with 410 responses automatically include a `<meta name="robots" content="noindex" />` tag to ensure search engines don't index these pages. | ||
|  | ||
| ## Best Practices | ||
|  | ||
| 1. **Use judiciously**: Only use 410 for content that has been deliberately removed and won't return. | ||
|  | ||
| 2. **Provide alternatives**: When showing a 410 page, offer users alternative content when possible. | ||
|  | ||
| 3. **Maintain consistency**: Be consistent in your approach to removed content across your application. | ||
|  | ||
| 4. **Monitor 410 responses**: Keep track of URLs returning 410 responses to identify patterns or issues. | ||
|  | ||
| 5. **Consider redirection**: For high-traffic pages that have been removed, consider redirecting to related content instead of showing a 410 page. | ||
|  | ||
| ## Common Use Cases | ||
|  | ||
| - Discontinued products in e-commerce | ||
| - Deleted blog posts or articles | ||
| - Removed user profiles | ||
| - Content that violates terms of service | ||
| - Content removed for legal reasons | ||
| - Expired time-limited content (contests, promotions) | ||
        
          
          
            114 changes: 114 additions & 0 deletions
          
          114 
        
  docs/01-app/04-api-reference/02-file-conventions/gone.mdx
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| --- | ||
| title: gone.js | ||
| description: API reference for the gone.js file convention. | ||
| --- | ||
|  | ||
| A `gone.js` file is used to render UI when the [`gone()`](/docs/app/api-reference/functions/gone) function is thrown within a route segment. | ||
|  | ||
| ```tsx filename="app/posts/[slug]/gone.js" | ||
| export default function PostGone() { | ||
| return ( | ||
| <div> | ||
| <h1>Post Removed</h1> | ||
| <p> | ||
| This content has been permanently removed and is no longer available. | ||
| </p> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
|  | ||
| ## Props | ||
|  | ||
| `gone.js` components do not receive any props. | ||
|  | ||
| ## Returns | ||
|  | ||
| `gone.js` components should return valid JSX. | ||
|  | ||
| ## Behavior | ||
|  | ||
| ### Status Code | ||
|  | ||
| When the `gone()` function is thrown in a route segment, the HTTP status code is set to `410 Gone`. | ||
|  | ||
| ### File Conventions | ||
|  | ||
| - A `gone.js` file will handle all 410 errors in the current segment and any nested segments below it. | ||
| - The closest `gone.js` file will be used. | ||
| - If no `gone.js` file is found, the runtime will use the default `gone` UI. | ||
| - You can use a `gone.js` file at the root of your `app` directory to create a custom 410 page for your entire application. | ||
|  | ||
| ### Nesting | ||
|  | ||
| You can use `gone.js` with route segments to create custom "gone" UIs for specific parts of your application: | ||
|  | ||
| ``` | ||
| app/ | ||
| ├── posts/ | ||
| │ ├── [slug]/ | ||
| │ │ ├── gone.js # Handles 410 errors for /posts/[slug] | ||
| │ │ └── page.js | ||
| │ └── gone.js # Handles 410 errors for /posts | ||
| ├── products/ | ||
| │ └── [...slug]/ | ||
| │ ├── gone.js # Handles 410 errors for /products/[...slug] | ||
| │ └── page.js | ||
| └── gone.js # Handles 410 errors for all other routes | ||
| ``` | ||
|  | ||
| ### SEO | ||
|  | ||
| - The `gone.js` component automatically includes a `<meta name="robots" content="noindex" />` tag to prevent search engines from indexing the page. | ||
| - The HTTP status code 410 informs search engines that the resource is permanently gone, which helps with faster removal from search indexes compared to a 404 status. | ||
|  | ||
| ### Examples | ||
|  | ||
| #### Basic Usage | ||
|  | ||
| ```tsx filename="app/posts/[slug]/gone.js" | ||
| export default function PostGone() { | ||
| return ( | ||
| <div className="container"> | ||
| <h1>Content Removed</h1> | ||
| <p>This post has been permanently removed from our site.</p> | ||
| <p> | ||
| You may be interested in <a href="/posts">our other articles</a>. | ||
| </p> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
|  | ||
| #### With Layout | ||
|  | ||
| ```tsx filename="app/gone.js" | ||
| export default function Gone() { | ||
| return ( | ||
| <div className="error-container"> | ||
| <div className="error-content"> | ||
| <h1>410 - Content Gone</h1> | ||
| <p>This content has been permanently removed.</p> | ||
| <button onClick={() => window.history.back()}>Go Back</button> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
|  | ||
| ## Pages Router Support | ||
|  | ||
| If you're using the Pages Router, you can create a `pages/410.js` file to create a custom 410 Gone page: | ||
|  | ||
| ```tsx filename="pages/410.js" | ||
| export default function Custom410() { | ||
| return ( | ||
| <div> | ||
| <h1>410 - Content Permanently Removed</h1> | ||
| <p> | ||
| The requested content has been permanently removed from this server. | ||
| </p> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
There was a problem hiding this comment.
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
notFoundimport which is required since it's used on line 50. Please update the import statement to include both functions:This will ensure the example works correctly when copied by developers.
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.