bext.dev
DocsGuides
Guides4 min read
On this page

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#

toml
[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.

code
GET /sitemap.xml
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:

ts
// 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:

code
GET /robots.txt
code
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:

code
GET /feed.xml

Each file must include frontmatter with at least title and date:

markdown
---
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:

tsx
// 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:

toml
# 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.

code
GET /__bext/api/og?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:

tsx
export const seo = {
  title: "Product Launch",
  ogImage: "/__bext/api/og?title=Product+Launch&subtitle=Coming+Soon"
};

Meta Tag Management#

bext automatically manages <head> meta tags from page-level SEO exports:

tsx
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:

html
<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 hreflang alternate link tags for each configured locale.

Tip

OG images generated by /__bext/api/og are PNG — social platforms reject SVG og:image values. If you self-host OG images, ensure they are raster (PNG or JPEG) before setting the og:image meta tag.

  • Image Optimization — on-the-fly resize and format conversion used by the OG image pipeline
  • Caching — sitemap and feed responses are ISR-cached; see TTL configuration
  • i18n capability — hreflang alternate tag generation for multi-locale sites
  • Frameworks — Routing — how bext discovers routes for sitemap generation
  • Server Actions — combine JSON-LD exports with server-side data fetching
Edit this page ↗Need a hand? ↗
FIND YOUR NEXT STEP

Start with a topic, a command, or a question.