I published a new post on abd.dev and waited for it to appear in the sitemap.
It did not.
The page was live. The homepage knew about it. The sitemap was still serving a version from four hours earlier.
That made no sense because the route explicitly had a one-hour revalidation window.
At least, the source code did.
The production artifact did not.
The configuration looked correct
The sitemap was implemented at
/sitemap.xml, exactly where a sitemap belongs.The route exported a
revalidate value so Next.js would rebuild it periodically. On paper, this should have produced an ISR route: serve the cached sitemap quickly, regenerate it after the cache window, then include newly published posts.Instead, Vercel kept serving the old file.
The first instinct was to blame caching. Hit the route again. Wait for the window. Check headers. Trigger another request. Maybe the regeneration was delayed.
None of that changed anything because there was no regeneration configuration in production to trigger.
The build artifact told the truth
Next.js treats certain filenames as metadata routes.
sitemap.xml is one of them.That special handling changed how the route was emitted. Instead of producing a normal dynamic or ISR route, the build classified it as a static metadata file.
The
revalidate export silently disappeared from the production route definition.The source said one hour.
The deployed artifact said static forever.
There was no type error. No build warning. No failed deployment. The application accepted a configuration that the deployment output did not preserve.
That distinction matters. We spend a lot of time reviewing source code as if it is the system. It is not. It is input to the system that actually gets built and deployed.
When framework behavior is involved, the generated artifact is often the only honest specification.
The fix was to stop fighting the special route
I moved the handler to a normal path:
/pages-sitemap.xml.Then I added a
beforeFiles rewrite so the public URL remained /sitemap.xml:async rewrites() { return { beforeFiles: [ { source: "/sitemap.xml", destination: "/pages-sitemap.xml" }, ], }; }
The public contract did not change. Search engines still request
/sitemap.xml.But Next.js now sees the implementation as a normal route rather than a metadata file. The build emits the ISR configuration correctly, and the sitemap can refresh when new posts are published.
The missing post appeared after deployment.
The debugging lesson
This bug survived because every layer looked individually reasonable.
- The post was public.
- The database contained it.
- The sitemap handler could find it.
- The route exported
revalidate.
- The deployment succeeded.
/sitemap.xmlreturned HTTP 200.
The failure lived between source semantics and deployment semantics.
That is where framework magic becomes dangerous. A reserved filename, compiler transform or hosting optimization can change the meaning of code without making the source look wrong.
When production behavior contradicts configuration, inspect what the framework actually emitted.
Do not keep arguing with the source code.
The build artifact is running. Your intention is not.
