Loading...
I recently faced a challenge while using Next.js (App Router) with Server-Side Rendering (SSR). My application was returning Soft 404 pages for certain routes, which was affecting both user experience and SEO. After some investigation, I found a solution that worked for me, and I wanted to share it here.
When I use SSR in Next.js, all the 404 errors in the SSR pages were being treated as Soft 404s. This means that instead of returning a proper 404 status code, the server was returning a 200 status code with a page that indicated a 404 error. This behavior can confuse search engines and lead to poor SEO performance.
General
Request URL: https://sivothajan.me/blogs/i/999999999
Request Method: GET
Status Code: 200 OK
Remote Address: 216.198.79.1:443
Referrer Policy: strict-origin-when-cross-origin
In the above example, when requesting a non-existent blog post with the ID 999999999, the server responded with a 200 OK status code instead of 404 Not Found, creating a Soft 404 that can confuse search engines and harm SEO. The server successfully delivered page content, even though it represents a 404 error.
After researching the issue, I found that Next.js SSR is streaming the HTML content to the client. When a 404 page is rendered, the server sends a 200 status code along with the HTML content of the 404 page. This is because the server successfully delivered the content, even though it indicates a 404 error.
This behaviour is by design in Next.js (App Router) SSR, but it can lead to Soft 404 issues as search engines may not interpret the page correctly.
This issue persists for a long time. I found that this problem has been discussed in GitHub discussions and issues. The Next.js team (Vercel) is aware of the issue, but they have not yet provided a built-in solution to handle it.
In a GitHub discussion, a Vercel employee/ Next.js Repo maintainer mentioned that,
TLDR: because of streaming rendering and the preinitialization (preamble part of the request) in React you can't modify the status / headers, that's also why you can't set cookies / set headers during server components rendering.
Which means that due to the nature of streaming rendering in React and Next.js, it's not possible to modify the status code or headers once the response has started streaming.
This is marked as Answered, but there is no further update from the Next.js team regarding a fix or workaround for this issue.
The relevant GitHub issue is opened but not made any progress. means that this is still an open issue without a definitive solution from the Next.js team.
To resolve this issue, I implemented a Pre-SSR Route Validation via Middleware in Next.js
The middleware only wakes up when a request is made to a SSR page. The implementation is simple and can be done in a few steps:
Create a proxy.ts file in the src directory or any other suitable location in your project. (read the documentation for more details)
Export a matcher function that matches the SSR routes.
export const config = {
matcher: [
'/blogs/i/:id*',
'/blogs/n/:name*'
// Add more SSR routes as needed
]
};
Implement the proxy logic to check if the requested route exists. If it doesn't, rewrite the request to a custom 404 page.
import { NextRequest, NextResponse } from 'next/server';
/**
* Middleware that validates incoming routes against
* a list of allowed paths.
*
* This function is async because valid paths are fetched
* from an external API. If paths were hardcoded locally,
* this middleware would not need to be async.
*/
export default async function proxy(req: NextRequest) {
console.log('🔥 PROXY HIT:', req.nextUrl.pathname);
// Normalize the pathname for reliable matching
const pathname = req.nextUrl.pathname.replace(/\/$/, '').toLowerCase();
/**
* Fetch valid paths from an external source (CMS, database, etc).
* Because this operation is asynchronous, the middleware itself
* must be declared as async.
*/
let validPaths: string[] = [];
try {
validPaths = await fetchValidPathsFromExternalAPI();
} catch (error) {
// Fail fast if we cannot determine valid routes
console.error('Failed to fetch valid paths:', error);
return NextResponse.rewrite(new URL('/404', req.url), { status: 404 });
}
// Reject unknown routes
if (!validPaths.includes(pathname)) {
return NextResponse.rewrite(new URL('/404', req.url), { status: 404 });
}
// Allow valid requests to proceed
return NextResponse.next();
}
Test your application to ensure that the proxy correctly handles 404 errors and prevents invalid routes from reaching the streaming SSR pipeline, reducing Soft 404s.
💡 Tip: Avoid hardcoding paths; serve valid routes from a CDN-cached JSON or KV store (I used Cloudflare static hosting).
🚨 Warning: Never fetch paths per request without caching; middleware runs on every match and will wreck latency.
⚠️ Note: Even if your
/404page returns a real 404,NextResponse.rewrite()can still result in a200 OKin some cases. Explicitly setting{ status: 404 }ensures the response has the correct status, preventing Soft 404s. You can find relevant GitHub issue here
By implementing Pre-SSR Route Validation via Middleware in Next.js, I was able to resolve the Soft 404 issue in my SSR application. This solution ensures that 404 errors are correctly handled and that search engines can accurately interpret the status of my pages.
💡 Note: This is a temporary workaround until Next.js provides a built-in solution. Keep an eye on Next.js releases for updates and improvements in handling SSR 404s.
HTTP Status Code for Server Components - but marked as answered404 page isn't server rendered when using notFound()NextResponse.rewrite(url, { status }) in middleware