Configuration
Every field in agentblog.config.ts, its type, its default, and what breaks when it is wrong.
agentblog.config.ts sits at your project root, next to package.json and
next.config.ts, including in a src/ layout.
Exactly one module reads it: lib/config.ts. That is enforced by agentblog doctor, because @/ maps to the root in a flat layout and to src/ in a
src/ layout, so one import specifier cannot be right in both. With a single
importer, fixing a src/ project is a one-line change rather than a search
across forty files.
The shape
import { defineConfig } from '@/lib/define-config'
import { mdxSource } from '@/lib/sources/mdx'
export default defineConfig({
siteUrl: 'https://yourdomain.com',
brand: {
name: 'Your Brand',
logo: { url: '/logo.png', width: 512, height: 512 },
sameAs: ['https://github.com/yourbrand'],
},
source: mdxSource({ dir: 'content/blog' }),
})siteUrl, brand, and source are required. Everything else has a default.
Every field
| Field | Type | Default | Notes |
|---|---|---|---|
siteUrl | string | required | https, no trailing slash, no path. Trailing slashes are stripped before validation |
locale | string | 'en_US' | Emitted as og:locale and as inLanguage in the structured data |
brand.name | string | required | Becomes og:site_name and Organization.name |
brand.logo | { url, width, height } | required | width and height are required, not optional |
brand.sameAs | string[] | [] | Absolute URLs only. A bare handle is rejected at build time |
source | ContentSource | required | See content sources |
deployHook | string | none | Required, at compile time, when the source needs a rebuild to publish |
revalidate | number | 3600 | Revalidation window in seconds for the blog routes |
postsPerPage | number | 12 | Index pagination |
noindexTagsBelow | number | 5 | Tag pages below this post count are marked noindex |
indexnow | { enabled, key? } | { enabled: false } | The key is 8 to 128 characters |
verification | { google?, yandex?, yahoo?, other? } | {} | Maps straight onto the Next.js metadata verification fields |
aiAccess | { train?, search?, agent? } | all true | Emitted into robots.txt |
preflight | boolean | true | Build-time config linting |
trailingSlash | boolean | false | Must match next.config.ts |
defaultAuthor | string | none | Author slug used when a post omits one |
Unknown keys are ignored rather than rejected, so an invented field is silently dead. Change values, not structure.
The fields worth understanding
siteUrl
Every canonical link, sitemap entry, RSS link, and structured data identifier is composed from this string, so a wrong value is wrong in about forty places at once. Point it at the domain readers actually visit, never at a preview deployment.
A trailing slash would produce https://site.dev//blog/post in every canonical,
so it is stripped and the value is revalidated before use.
Never build a URL by concatenating onto config.siteUrl. Use absoluteUrl,
postUrl, categoryUrl, tagUrl, and authorUrl from lib/config.ts. They
are the only code that knows your trailing-slash policy and the rule that a path
ending in a file extension never takes one.
brand.sameAs
The single most valuable property in the file. It becomes Organization.sameAs,
which is how a search engine or an assistant resolves "the company that
published this" to a real entity rather than to a string that looks like a
company name. Entity resolution is what lets a model cite you by name and
attribute the claim correctly.
List profiles a third party can verify: LinkedIn, Crunchbase, GitHub, YouTube, Wikidata. A bare handle is rejected, because it disambiguates nothing.
brand.logo
width and height are required because Google's Organization markup needs
both, and a logo without dimensions is one of the most common structured data
errors on the web. A relative url resolves against siteUrl.
deployHook
Required when your content source declares that publishing needs a rebuild. Set it to a rebuild trigger URL so the publish webhook can rebuild before pinging IndexNow. In the other order you are telling a crawler to fetch a URL that was never built.
You will not forget this one: the file stops type checking until it is set.
noindexTagsBelow
Thin tag pages are the classic way a blog generates hundreds of near-empty
indexable URLs and spends its own crawl budget on them. Categories are always
indexable. Tags have to earn it. Set it to 0 to index every tag.
indexnow
indexnow: { enabled: true, key: process.env.INDEXNOW_KEY }The key is read from the config first and falls back to INDEXNOW_KEY in the
environment, so the secret never has to live in this committed file.
The matching <key>.txt file must be served from your domain root, UTF-8,
containing the key and nothing else, or every submission comes back 403. A key
hosted at a subpath only authorises URLs under that subpath. agentblog doctor --fix writes the file for you.
The limit is 10,000 URLs per request, not per day.
aiAccess
aiAccess: { train: true, search: true, agent: true }A forward-looking seam. Three standards are converging on machine-readable AI
usage preferences, and all of them attach to robots.txt or to HTTP headers.
None is implemented yet. When one lands, this same object emits it with no
change to your config file.
Setting train: false is a real tradeoff rather than a free one. Several
crawlers are multi-purpose, so opting out of training can also opt you out of
the search index that would have cited you.
preflight
On by default. It reads your next.config off disk once per process during dev
and build and warns when a required setting is missing or narrowed. It never
throws and it does nothing at request time.
Set it to false only once you know your config is correct and you want the
warning gone. That is the supported way to switch it off. Deleting the preflight
import from app/blog/layout.tsx is not, because then nothing tells you when a
later edit breaks the config again.
trailingSlash
Must match trailingSlash in next.config.ts. The URL helpers compose
canonicals from this value, so two different answers means every canonical
points at a URL that immediately redirects to its other form. Search engines
treat that as a redirect chain and AI crawlers frequently do not follow it at
all.
Why defineConfig rather than satisfies
defineConfig is an identity function that exists for its type signature. It
gives contextual inference, which is what lets the deployHook requirement
depend on which content source you passed. satisfies checks an object against
a fixed type and cannot do that.
Concretely: swap mdxSource for a source that needs a rebuild to publish, and
the file stops type checking until you supply a deployHook. A runtime surprise
becomes a compile error, which is the highest-value thing the type layer does
here.
Validation
Your config is validated once, when lib/config.ts is first imported. A bad
value fails the build with a message naming the key, rather than producing
undefined inside a structured data graph three files later.