Configure talizen.config.ts
Implementation spec for AI agents: field groups and evaluation timing, html/body tag attributes, head/bodyEnd injected code, and per-request (ctx) => value fields.
Follow this specification when an AI coding agent configures site-level behaviour: dependencies, routing, metadata, the document shell and injected code. talizen.config.ts is the single entry point for site-level configuration, and the rules below are implementation constraints, not optional UI suggestions.
What belongs here, and what does not
One rule decides it: anything with its own URL is a file; anything without a URL that applies to every page is configuration.
Configuration
Fields in talizen.config.ts have no URL of their own. They are the site-level part of every page's HTML.
Endpoint files
/robots.ts → /robots.txt, /sitemap.ts → /sitemap.xml, /llms.ts → /llms.txt. Each produces one standalone response body, so each is a file.
Page files
/pages/about.tsx → /about. A page's own metadata / generateMetadata applies to that page only.
No layout.tsx
The platform has no such convention. Creating one does not error — it is silently ignored. Express the document shell with html / body / head / bodyEnd below.
Fields and when they are evaluated
The config file must export default a plain object. Do not import packages (except import type) and do not use defineConfig.
Fields fall into two groups, split by whether the platform needs them before a request exists:
- Static values only:
importMap,i18n,redirects. Bundling and route building both happen ahead of any request. - May be
(ctx) => value:metadata,html,body,head,bodyEnd,viewport. These only shape the rendered HTML, so they can be evaluated per request.
Hard requirement: writing
importMap,i18norredirectsas a function fails at site load. Those three are read before a request exists, so a function there would be ignored entirely — the page would look fine while the config had no effect. The platform errors instead of degrading silently.
Canonical configuration
import type { TalizenConfig, TalizenConfigContext } from 'talizen'
export default {
// Static: bundling and route building happen before a request
importMap: {
imports: { 'framer-motion': 'https://esm.talizen.com/framer-motion@12' },
},
i18n: {
defaultLocale: 'zh-CN',
locales: ['zh-CN', 'en'],
},
redirects: [
{ source: '/old', destination: '/new', permanent: true },
],
// Document output: a value, or (ctx) => value
metadata: (ctx: TalizenConfigContext) => ({
title: {
template: ctx.locale === 'en' ? '%s | Acme' : '%s | Acme',
default: 'Acme',
},
description: ctx.locale === 'en' ? 'English description' : '中文描述',
}),
html: { className: 'dark scroll-smooth' },
body: { className: 'antialiased bg-neutral-950 text-neutral-100' },
head: (ctx: TalizenConfigContext) =>
ctx.host.endsWith('.cn')
? '<script async src="https://hm.baidu.com/hm.js?xxx"></script>'
: '<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXX"></script>',
bodyEnd: '<script src="/widget.js" defer></script>',
} satisfies TalizenConfig
Use satisfies TalizenConfig rather than as: the former catches mistakes such as a static-only field written as a function while you type, the latter suppresses them.
html and body: document tag attributes
html and body are attribute objects applied directly to the server-rendered <html> / <body>.
classNameandclassare equivalent; either works.- Do not set
lang. The platform fills it from the current locale. Set it only to override that inference. - Attribute names and values are HTML-escaped.
- This is the correct home for a dark-mode class: attributes declared in config land in the server HTML, so there is no flash from a client-side script.
Hard requirement: shared interactive UI such as a header nav or footer does not belong here. The document shell is a static declaration and cannot hydrate; keep that UI in components that pages import.
head and bodyEnd: injected code
Both are HTML strings: head is injected before </head>, bodyEnd before </body>.
They supersede customCode.head / customCode.body, and the difference that matters is that they can be evaluated per request — for example a different analytics script per domain, which static customCode cannot express.
- Injection order is: platform tags →
customCode→head/bodyEnd. The newer fields can therefore override the older ones. - Mark third-party scripts
asyncordeferyourself; a synchronous script inheaddelays first render. - Do not repeat tags that
metadataalready expresses (title,description, Open Graph) or the page emits two of them.
The per-request ctx
Fields written as functions are called on every render and receive only these fields:
interface TalizenConfigContext {
locale: string // current locale; empty string on single-language sites
locales?: string[] // i18n.locales
defaultLocale?: string // content baseline locale
routingDefaultLocale?: string // the unprefixed default locale of this host
host: string // request host, for per-domain branching
path: string // request path with the locale prefix removed
}
Hard requirement: the ctx deliberately offers no cookies and no CMS access. Reading a cookie would make the page cache per cookie, and fetching data at the config layer cannot participate in cache invalidation. Do not fetch data in config fields; keep them synchronous and simple. If a function throws, the request fails rather than silently dropping the site-level config.
Metadata layering
Site-level metadata comes from config; page metadata layers on top.
- A
title.templatein config wraps the literal title a page provides. title.defaultis emitted as-is and is not wrapped by the template — it means "use this when the page gives no title".- A page's
title: { absolute: '…' }bypasses the template. - To vary site metadata by language, write
metadataas a function and branch onctx.locale. Themetadata._i18nfield still works but is deprecated; do not use it in new code.
Implementation checklist
export defaulta plain object; no package imports exceptimport type; nodefineConfig.importMap,i18nandredirectsare static values.- Write a field as a function only when it genuinely branches on
localeorhost; otherwise write the value directly. html/bodydo not setlang.- Third-party scripts carry
asyncordefer. - Use
satisfies TalizenConfigso the types catch mistakes at authoring time. - After pushing, view source on the real preview URL and confirm the
<html>/<body>attributes, the<title>and the position of injected scripts; on multilingual sites verify each locale prefix.
Forbidden implementations
- Do not create
layout.tsx: the platform has no such convention and it is silently ignored. - Do not write
importMap,i18norredirectsas functions. - Do not fetch data in config fields (CMS, fetch, reading cookies).
- Do not hand-write SEO tags in
head/bodyEndthatmetadataalready expresses. - Do not keep using
customCodefor new work: it is static and cannot branch on locale or host. - Do not put shared interactive chrome in the document shell; those are components that pages import.
- Do not nest
viewportinsidemetadata; it is its own top-level field.
