# Troubleshooting

Source: https://docs.agentblog.dev/troubleshooting
Summary: 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](/troubleshooting/cdn-blocking-crawlers), because it is the
most common cause and the least visible one.

## The build fails after I edited content [#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.

```text
[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:

1. **Keep the `editorial` slug 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.
2. **Change the slug and the posts in the same commit.** Edit the `author:` line
   in every file under `content/blog`, and `defaultAuthor` in
   `agentblog.config.ts`, alongside the roster.
3. **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 [#the-article-text-is-not-in-the-raw-html]

```bash
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 [#the-title-is-in-the-body-instead-of-the-head]

```bash
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:

```bash
npx agentblog@latest doctor --fix
```

Read 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 [#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.

```css title="app/globals.css"
@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 [#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 [#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 [#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 [#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 [#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.

```bash
npx agentblog@latest doctor
```

It 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 [#turbopack-warns-about-dynamic-filesystem-access]

Every build prints six of these, three from `lib/preflight.ts` and three from
`lib/sources/mdx.ts`.

```text
Warning: Dynamic filesystem access causes tracing of the whole project
```

**These 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:

```ts
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 [#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:

```text
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:

```ts title="next.config.ts"
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 [#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.

```ts
images: {
  qualities: [75, 90]
}
```

`agentblog doctor --fix` writes this.

## The build fails on a social image [#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 [#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 [#indexnow-returns-403-or-422]

* **403:** the key is invalid or the key file is missing. `public/<key>.txt` has
  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 [#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](/project/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 [#undoing-an-install]

```bash
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 place
```

`revert` 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.
