SEO Utilities
bext generates SEO artifacts automatically from your content and configuration. Instead of maintaining separate sitemap files, feed templates, and meta tag logic across your app, declare your intent in bext.config.toml and let the server handle the rest.
Configuration
[seo]
enabled = true
site_url = "https://example.com" # Canonical base URL (required)
site_name = "My App"
default_locale = "en"
# Sitemap generation
[seo.sitemap]
enabled = true
include_static = true # Include all static routes
exclude_patterns = ["/admin/**", "/api/**", "/_bext/**"]
default_changefreq = "weekly" # daily, weekly, monthly, yearly, never
default_priority = 0.5 # 0.0 - 1.0
# robots.txt
[seo.robots]
enabled = true
disallow = ["/admin", "/api", "/_bext"]
allow = ["/api/public"]
sitemap_url = "https://example.com/sitemap.xml" # auto-set if sitemap enabled
# RSS/Atom feed
[seo.feed]
enabled = true
title = "My App Blog"
description = "Latest posts from My App"
path = "/feed.xml" # Serve at this path
content_dir = "content/blog" # Scan this directory for entries
format = "rss" # "rss" or "atom"
max_items = 50
# Open Graph image generation
[seo.og_image]
enabled = true
width = 1200
height = 630
background = "#1a1a2e"
text_color = "#ffffff"
font_size = 48
Automatic Sitemap Generation
bext crawls your route tree at startup and generates a sitemap.xml that stays in sync with your pages. Static routes, dynamic routes with known parameters, and ISR-cached pages are all included automatically.
GET /sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2026-04-01T12:00:00Z</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://example.com/products</loc>
<lastmod>2026-03-28T08:30:00Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
Override defaults per page by exporting SEO metadata from your route:
// src/pages/products.tsx
export const seo = {
priority: 0.9,
changefreq: "daily",
};
robots.txt
bext generates robots.txt from config and serves it at the root:
GET /robots.txt
User-agent: *
Disallow: /admin
Disallow: /api
Disallow: /_bext
Allow: /api/public
Sitemap: https://example.com/sitemap.xml
RSS/Atom Feeds
Point content_dir at a directory of Markdown or MDX files with frontmatter, and bext generates a valid RSS 2.0 or Atom feed:
GET /feed.xml
Each file must include frontmatter with at least title and date:
---
title: Launching bext 2.0
date: 2026-03-15
description: Major release with HTTP/3 and WAF support.
---
Post content here...
bext automatically sets <link rel="alternate" type="application/rss+xml"> in your HTML responses so feed readers discover the feed.
JSON-LD Structured Data
Inject JSON-LD structured data into SSR responses by exporting a jsonLd object from your page:
// src/pages/products/[slug].tsx
export function getServerSideProps({ params }) {
const product = getProduct(params.slug);
return {
props: { product },
seo: {
jsonLd: {
"@context": "https://schema.org",
"@type": "Product",
"name": product.name,
"description": product.description,
"image": product.imageUrl,
"offers": {
"@type": "Offer",
"price": product.price,
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
}
}
};
}
bext injects the JSON-LD as a <script type="application/ld+json"> block in the <head> of the rendered HTML.
For common schemas, bext provides built-in helpers. Set the page type and bext generates the appropriate schema:
# Per-page SEO in route config
[[seo.pages]]
path = "/"
type = "WebSite"
name = "My App"
search_url = "https://example.com/search?q={search_term_string}"
[[seo.pages]]
path = "/about"
type = "Organization"
name = "My Company"
logo = "https://example.com/logo.png"
Open Graph Image Generation
bext generates OG images on-the-fly for pages that do not have an explicit og:image meta tag. The generated image renders the page title over a branded background.
GET /_bext/og-image?title=My+Page+Title&subtitle=example.com
Configure the default style in [seo.og_image]. The generated image is cached in the same tiered cache used by the image optimization pipeline.
Use it in your page metadata:
export const seo = {
title: "Product Launch",
ogImage: "/_bext/og-image?title=Product+Launch&subtitle=Coming+Soon"
};
Meta Tag Management
bext automatically manages <head> meta tags from page-level SEO exports:
export const seo = {
title: "My Page",
description: "A description for search engines",
canonical: "https://example.com/my-page",
openGraph: {
title: "My Page - Example",
description: "Optimized for social sharing",
image: "/images/og-hero.jpg",
type: "article"
},
twitter: {
card: "summary_large_image",
site: "@myapp"
}
};
This generates:
<title>My Page</title>
<meta name="description" content="A description for search engines" />
<link rel="canonical" href="https://example.com/my-page" />
<meta property="og:title" content="My Page - Example" />
<meta property="og:description" content="Optimized for social sharing" />
<meta property="og:image" content="/images/og-hero.jpg" />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@myapp" />
Canonical URLs
bext automatically generates <link rel="canonical"> for all pages based on site_url plus the request path. This prevents duplicate content issues from trailing slashes, query parameters, or alternative domains.
For pages with explicit canonical overrides (e.g. paginated content pointing to page 1), use the canonical field in your SEO export.
If i18n is enabled, bext also generates <link rel="alternate" hreflang="..."> tags for each configured locale.