Content Engine

Content Delivery & SEO

Deliver Content Engine records over the web with TanStack Start, automatic 308 redirects, plugin route manifests, and rich SEO metadata.

Content Engine provides delivery abstractions to turn your schema into public URLs. It handles the heavy lifting: automatic 308 redirects when slugs change, Open Graph tags, canonical links, hreflang alternates, and XML sitemaps.

Best of all: in VitNode, delivery lives in your plugin first. That means your content types and their public views ship together as one reusable package.

Step-by-Step Delivery Setup

Enable Delivery in your Content Definition

Define your content type with delivery enabled. Configure its base path, SEO fields, and sitemap settings:

plugins/example/src/content/article.ts
import { defineContentType, field } from '@vitnode/core/content'

export const articleContentType = defineContentType({
  id: 'example.article',
  tableName: 'example_articles',
  publication: true,
  editorial: true,
  delivery: {
    basePath: '/articles',
    redirects: true, // Automatically 308-redirects old slugs on rename
    seo: {
      titleField: 'title',
      descriptionField: 'excerpt',
    },
    sitemap: {
      changefreq: 'weekly',
      priority: 0.8,
    },
  },
  fields: {
    title: field.text({ required: true }),
    slug: field.slug({ from: 'title' }),
    excerpt: field.textarea({ nullable: true }),
  },
})

Claim the Dynamic URL in your Plugin Manifest

Plugins claim routes as plain data. Notice VitNode's parameter spelling: :slug, which TanStack Start maps directly to $slug.

plugins/example/src/routes/manifest.ts
import type { PluginRouteDefinition } from '@vitnode/core/routing'

export const routes: PluginRouteDefinition[] = [
  {
    entry: 'routes/article-page',
    id: 'article-view',
    namespaces: ['@vitnode/example.articles'],
    path: '/articles/:slug',
  },
]

Implement the Plugin Route Module

Export a default component and a route definition using definePluginRoute. No framework dependencies needed—just clean, isomorphic code:

plugins/example/src/routes/article-page.tsx
import type { PluginRoutePageProps } from '@vitnode/core/routing'
import { definePluginRoute } from '@vitnode/core/routing'
import { useTranslations } from 'use-intl'
import { fetchArticle } from '../api/fetch-article'

interface ArticleData {
  excerpt: string
  publishedAt: string
  title: string
}

export const route = definePluginRoute({
  load: async ({ params }) => {
    const article = await fetchArticle(params.slug)
    return article
  },
  head: ({ loaderData }) => ({
    title: loaderData?.title,
    description: loaderData?.excerpt,
  }),
})

const ArticlePage = ({ loaderData }: PluginRoutePageProps<ArticleData>) => {
  const t = useTranslations('@vitnode/example.articles') 

  return (
    <article className="container mx-auto max-w-3xl py-8 px-4">
      <header className="mb-6 flex flex-col gap-2">
        <h1 className="text-3xl font-bold tracking-tight text-balance">
          {loaderData.title}
        </h1>
        <p className="text-sm text-muted-foreground">
          {t('published_on', { date: loaderData.publishedAt })}
        </p>
      </header>

      <div className="prose leading-relaxed">
        <p>{loaderData.excerpt}</p>
      </div>
    </article>
  )
}

export default ArticlePage

Optional: Override from the Host Application

Need a totally unique design for this one site? You can declare a route file directly in your TanStack Start app:

apps/web/src/routes/_main/articles.$slug.tsx
import { createFileRoute } from '@tanstack/react-router'
import { pageHead } from '#/lib/page-head'

export const Route = createFileRoute('/_main/articles/$slug')({
  head: () =>
    pageHead({
      title: 'Article - VitNode',
      robots: 'index, follow',
    }),
  component: AppArticlePage,
})

function AppArticlePage() {
  const { slug } = Route.useParams()
  return <div>Custom host layout for article: {slug}</div>
}

Automatic SEO Capabilities

VitNode gives your content search engine superpowers without the manual headaches:

  • 308 Permanent Redirects: Renaming a slug automatically issues a 308 redirect from the old URL to the new one, saving your Google rank from 404 disasters.
  • Canonical URLs: Formatted cleanly using your application's base URL and canonical slug.
  • Hreflang Tags: Emits <link rel="alternate" hreflang="..." /> tags for active translations so multilingual crawlers stay happy.
  • XML Sitemaps: Pre-configured XML sitemaps served straight from the API.