Troubleshooting
The failures that actually happen, what each one looks like from the outside, and the fix for each.
Almost every failure here is silent. That is the shape of the problem rather than an accident of implementation: metadata in the wrong element still renders, a narrowed pattern still matches, a stale cache still returns 200. So the symptom is usually "nothing is wrong and nothing is working".
Start here. If the answer turns out to be "a CDN is blocking the crawler", that has its own page, because it is the most common cause and the least visible one.
The build fails after I edited content
This is the first thing most installs hit, and doctor sends you into it: it
warns that the example author roster is unedited, you edit it, doctor goes
green, and the next build fails. doctor reads your config, not your posts, so
the example posts are outside what it checks.
[agentblog] content/blog/how-ai-search-engines-read-your-blog.mdx is invalid:
author: unknown author slug "editorial". Add a record with that slug to
content/authors.json, or inline the author object in this post's frontmatter.Both example posts carry author: editorial, and editorial is the slug of the
first record in content/authors.json. Replacing the roster with your own
record under a different slug deletes a slug two files still name. The category
version of this error is identical in shape.
Three ways out, in the order they are usually right:
- Keep the
editorialslug and replace everything else in the record. The slug is an internal key. It appears in one URL,/authors/editorial, and nowhere else. Edit the name, bio, job title, and profile links. - Change the slug and the posts in the same commit. Edit the
author:line in every file undercontent/blog, anddefaultAuthorinagentblog.config.ts, alongside the roster. - Delete the example posts. They are the format specification, so read one first, but nothing depends on them existing.
The same trap has a quieter version. defaultAuthor in agentblog.config.ts
ships as a placeholder that matches no record. It only bites the first post you
write without an author in its frontmatter, which may be weeks later. Set it
to a real slug while you are in the file.
The article text is not in the raw HTML
curl -s -A "GPTBot" https://yoursite.com/blog/your-post | grep "a distinctive sentence"No match. Causes, in the order they occur:
Something in the render path is a client component. Look for 'use client'
anywhere the article body is rendered. Only the table of contents and the share
buttons are allowed to be client components, and both render meaningful content
on the server first.
Content is mounted on interaction. An accordion or a tab set that renders
its children only after a click. Use <details> instead: it is in the HTML
whether or not it is open.
The route was not prerendered. Check that generateStaticParams returns
every slug and is not sliced. agentblog doctor checks this by reading the
syntax tree rather than searching the text.
You are looking at the element inspector. It shows the page after JavaScript
has run. Use view source or curl.
The title is in the body instead of the head
curl -s -A "GPTBot" "$URL" | head -c 4000 | grep -q "<title>"Fails. The page rendered dynamically and Next.js streamed the metadata,
appending it to <body> instead of <head>.
Two fixes, and you want both. Prerender the post, so the metadata is in the
initial HTML for everyone. Then set htmlLimitedBots, so the AI crawlers get a
blocking render regardless:
npx agentblog@latest doctor --fixRead the value it writes carefully. It repeats the entire Next.js default list after the AI crawlers, and that is not redundancy: setting this config replaces the default rather than extending it. Dropping the tail removes Googlebot, Bingbot, Applebot, Twitterbot, LinkedInBot, Slackbot, Discordbot, and Facebook's crawler from the same treatment, which trades an AI win for a live SEO and social preview regression.
Article prose has no typography at all
Not slightly off. One font size, no visible heading hierarchy, no list markers, while the rest of the site looks right. Cards and layout are fine, because those are utilities in the components. Only the article body is affected.
You did not import the stylesheet.
@import 'tailwindcss';
@import '../styles/agentblog.css';From src/app/globals.css the path is ../../styles/agentblog.css, because the
file lands at the project root rather than next to app/.
Nothing catches this. It is not a doctor finding, the preflight check does not
look at your stylesheet, the build succeeds, and every crawler check passes,
because the HTML and the structured data are correct and only the presentation
is missing.
A new post is not in the sitemap or the feed
sitemap.xml, robots.txt, and the social image routes are cached route
handlers. Publishing a post and revalidating only its own path leaves all of
them stale.
The publish webhook that ships with the blog revalidates the sitemap and the
feed explicitly. If you wrote your own publish path, add them. agentblog doctor asserts it.
A published post 404s or renders a fallback
Your content source publishes without a rebuild, and the prerender step has not run since the last deploy, so the route was never built.
Check prerenderStrategy on your source. If publishing needs a rebuild it
should be 'deploy-hook', and your config will then refuse to compile without a
deployHook. The webhook fires the rebuild before it pings IndexNow, which is
the correct order. Pinging first tells a crawler to fetch a URL that does not
exist yet.
The crawler gets stale HTML right after publishing
revalidateTag takes a required second argument in Next.js 16, and 'max' on
the publish path means you invalidated nothing useful. Use { expire: 0 }.
Also worth knowing: updateTag(tag) takes a single argument and works only in
Server Actions. It throws in a route handler.
Social cards have no site name
View source on a built post and look for og:site_name. Missing means a segment
defined its own social metadata without spreading the shared defaults.
Next.js merges metadata shallowly, so defining the object at all in a child segment discards the parent's entire object. Nothing errors, no validator complains, and the types are correct.
Spread the defaults from lib/metadata.ts in every segment that sets them. The
same applies to the robots directives, where the loss costs you max-snippet
and max-image-preview, which is what permits full quotations in AI answers.
The blog does not match the rest of the site
Most likely a dark-mode colour variant or a palette utility crept into a
component you edited. The tokens already flip under .dark, so a dark-mode
colour override re-hardcodes the thing the token abstracts.
npx agentblog@latest doctorIt reports palette utilities, colour literals, and dark-mode colour variants in the installed components. A warning rather than an error, because you may have done it deliberately.
If the prose specifically looks wrong, check styles/agentblog.css for
hsl(var(--foreground)). Tailwind v4 stores complete oklch() values, so the
correct form is var(--foreground) with no wrapper. The old form fails silently
by producing an invalid colour that inherits.
Turbopack warns about dynamic filesystem access
Every build prints six of these, three from lib/preflight.ts and three from
lib/sources/mdx.ts.
Warning: Dynamic filesystem access causes tracing of the whole projectThese come from AgentBlog rather than from your code, and the build
succeeds. Both files read the filesystem on purpose. The preflight check reads
your next.config off disk to lint it, which is the entire reason it exists,
and the path is your project root rather than a subfolder Turbopack can scope.
The MDX source resolves the content directory from your config, so the path
cannot be a literal it can fold.
The consequence is real but it is a size and deploy-time cost rather than a correctness one: more files get copied next to your server bundle. On a blog that is usually a slower deploy and a larger function. It matters if you are near a platform size limit.
If it is a real problem, trim the trace in your own next.config.ts rather than
editing the installed files:
outputFileTracingExcludes: {
'/**': ['./public/**/*'],
}Exclude only what you are certain no route reads. Everything agentblog init
put in outputFileTracingIncludes has to stay: the MDX source really does read
content/blog/**, content/authors.json, and content/categories.json at
runtime, on /blog and whenever the publish webhook regenerates the sitemap and
the feed. Dropping one of them is the section below.
/blog returns a 500 and the post pages do not
The symptom is specific. Every post, category, and author page serves normally,
/blog returns a server error, and the deployment logs carry:
Error [AgentBlogSourceError]: [agentblog:mdx] getAllPosts failed
[cause]: Error: ENOENT: no such file or directory, open '/var/task/content/authors.json'The post pages are prerendered at build time, when the whole repository is on
disk. /blog reads searchParams for its ?page=N, so it renders on demand,
inside a function that only carries the files the build traced. The file named
in the error was not one of them.
Fix it by declaring every path your content source reads, not just the posts directory:
outputFileTracingIncludes: {
'/**': [
'./content/blog/**/*',
'./content/authors.json',
'./content/categories.json',
],
}npx agentblog doctor --fix writes that for you, reading the paths out of your
agentblog.config.ts. Two things about it are easy to get wrong by hand. The
roster files sit beside the posts directory rather than inside it, so
./content/blog/**/* alone ships the posts and leaves the authors behind. And
the key is /** rather than /blog/**, because sitemap.xml, feed.xml,
/authors/[slug], and /api/publish read content too.
The image optimizer returns 400
Next.js 16 rejects any image quality not listed in your config, and the default list contains only 75. The blog uses 75 for cards and 90 for hero images.
images: {
qualities: [75, 90]
}agentblog doctor --fix writes this.
The build fails on a social image
The site social image has an 8MB limit and the Twitter variant has 5MB. Exceeding either fails the build rather than warning.
Read fonts and logos once at module scope rather than per request, or every generated image pays for the read.
The preflight warning will not go away
It is telling you something. Read the line: it names the missing setting.
If your config is genuinely correct and the check is wrong, set preflight: false in agentblog.config.ts. That is the supported way to silence it.
Deleting the import from app/blog/layout.tsx is not, because then nothing
tells you when a later edit breaks the config again. If you think the check is
wrong, please open an issue: a false positive here is a bug that gets the import
deleted in a lot of repositories.
IndexNow returns 403 or 422
- 403: the key is invalid or the key file is missing.
public/<key>.txthas to be served from your domain root, UTF-8, containing the key and nothing else. A key hosted at a subpath only authorises URLs under that subpath. - 422: the submitted URL does not match the host that owns the key.
Both look identical to success from a caller that only checks whether the
request completed, which is why agentblog ping prints the code with its
meaning attached.
agentblog init refuses to run
| Message | Meaning |
|---|---|
components.json not found | Run npx shadcn@latest init first. AgentBlog will not, because that command picks a design system |
| Tailwind v3 detected | Not supported. See the roadmap |
app/blog already exists | You have a blog. --force overwrites it, and the conflicting paths are printed |
| Next.js 15 detected | The product is built on APIs specific to Next.js 16 |
Undoing an install
npx agentblog@latest revert # restore the last patch set
npx agentblog@latest revert --all # replay every backup, back to the start
npx agentblog@latest uninstall # reverse every patch, remove the AGENTS.md block
npx agentblog@latest uninstall --keep-env # the same, but leave the two secrets in placerevert on its own restores only the most recent backup, so a second doctor --fix can be undone without also unwinding your init.
uninstall lists the files the registry wrote rather than deleting them,
because by then some of them are yours.
Why the blog does not ship llms.txt
The evidence against llms.txt for content sites, the one case where it genuinely helps, and why these docs serve one while your blog will not.
When your CDN blocks crawlers
A perfectly installed blog can be invisible because the layer above it turns crawlers away, and blocking AI training can block Googlebot with it.