🔧 ProgrammierungRequest lifecycle: HandlerMapping HandlerAdapter resolvers(16.09.2026 um 00:08 Uhr)
🔧 ProgrammierungChapter 1 - The Funkiest of All Machines(16.09.2026 um 00:13 Uhr)
🔧 AI Nachrichten President Trump Called Nvidia’s Jensen Huang About A.I. Slowdown(15.09.2026 um 23:45 Uhr)
🔧 AI Nachrichten Google’s Simulated Fruit Fly Brain Did Not Write This Article(16.09.2026 um 00:00 Uhr)
🔧 ProgrammierungRequest lifecycle: HandlerMapping HandlerAdapter resolvers(16.09.2026 um 00:08 Uhr)
🔧 ProgrammierungChapter 1 - The Funkiest of All Machines(16.09.2026 um 00:13 Uhr)
🔧 AI Nachrichten President Trump Called Nvidia’s Jensen Huang About A.I. Slowdown(15.09.2026 um 23:45 Uhr)
🔧 AI Nachrichten Google’s Simulated Fruit Fly Brain Did Not Write This Article(16.09.2026 um 00:00 Uhr)

🔧 Programmierung 🕛 vor 3 Monaten 9 Min Lesezeit
0

How to build a blog with TanStack Start and Content Collections

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

When most devs consider building out a content site, their first inclination is often to reach for Astro, and for good reason. Astro comes with more or less everything you need to build out a content site.



Support for building content pages with Markdown/MDX and content collections is built into the framework. Astro even has templates to quickly scaffold a blog-style site.



However, TanStack Start has recently seen explosive growth, and there are some situations in which a Start app is preferable to Astro, even for publishing written content. You might have an existing Start app and want to add content pages. Or perhaps you just like staying within a pure React ecosystem.



The good news is that, whatever your reasons, it's quite easy to build a blog or content site using TanStack Start. In fact, this site is built with the exact stack covered in this article.






What we'll cover



In this guide we'll look at how to use the content-collections package alongside YAML frontmatter in MD / MDX files to build a performant and type-safe TanStack Start blog.



This will include configuring a collection for blog posts with Zod validation, setting up a dynamic route to render these collections, and rendering MDX content in React.



In an attempt to keep this guide as on-topic as possible, we won't delve into extraneous subjects such as styling, font configuration, or deployment. However, I highly recommend using Tailwind and is one of the best and easiest ways to go, unless you have a very specific reason to prefer a different provider. But for a content site, Cloudflare is often an excellent deployment target.






Project setup



Before setting up any blog content, you'll need a working TanStack Start app. If you already have one, feel free to skip ahead.



To create your Start app, run:




CODE
pnpx @tanstack/cli@latest create






And make your way through the setup wizard. This is the method I used when writing this tutorial.



Alternatively (and highly recommended), you can start from a template. See , is to use the following command:




CODE
pnpm create cloudflare@latest start-blog --framework=tanstack-start






This will scaffold a Start project that's ready to deploy to Cloudflare Workers.



Once the app is created, cd into your app directory and run:




CODE
pnpm install
pnpm dev









Set up content-collections






Install dependencies



Now that the app is up and running, you'll need to set up content-collections. First, install all necessary dependencies:




CODE
pnpm add -D @content-collections/core @content-collections/vite @content-collections/mdx zod









Update tsconfig.json



Now, add a content-collections path to your tsconfig.json. This allows us to import generated collections into our TypeScript code:




CODE
{
"compilerOptions": {
// ...
"paths": {
"~/*": ["./src/*"],
"content-collections": ["./.content-collections/generated"]
}
}
}









Update vite.config.ts



Next, import the @content-collections/vite plugin and add it to the plugins array in vite.config.ts:




CODE
import { defineConfig } from 'vite'
import { devtools } from '@tanstack/devtools-vite'

import { tanstackStart } from '@tanstack/react-start/plugin/vite'

import viteReact from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { nitro } from 'nitro/vite'
import contentCollections from '@content-collections/vite'

const config = defineConfig({
resolve: { tsconfigPaths: true },
plugins: [
contentCollections(),
devtools(),
nitro({ rollupConfig: { external: [/^@sentry\//] } }),
tailwindcss(),
tanstackStart(),
viteReact(),
],
})

export default config









Update .gitignore



If you're using git, it's best to add the generated directory .content-collections to your .gitignore:




CODE
.content-collections









Create content-collections.ts



Now it's time to define a collection. To do this, create a content-collections.ts file at the root of your project:




CODE
import { defineCollection, defineConfig } from '@content-collections/core'
import { compileMDX } from '@content-collections/mdx'
import { z } from 'zod'

const articleSchema = z.object({
title: z.string(),
createdAt: z.iso.date(),
updatedAt: z.iso.date(),
description: z.string(),
})

const articles = defineCollection({
name: 'articles',
directory: 'content/articles',
include: '**/*.mdx',
schema: articleSchema,
transform: async (document, context) => {
const mdx = await compileMDX(context, document)
return { ...document, mdx }
},
})

export default defineConfig({
content: [articles],
})






The code above does several things. The Zod schema is used to validate the frontmatter of each article MDX file you create. For example, if your article looks like this:




CODE
---
title: "How to build a blog with TanStack Start and Content Collections"
createdAt: Invalid Date
updatedAt: 2026-04-06
description: "This is a test description."
---

...






Zod validation will fail, since createdAt isn't a valid date, and the article won't be added to your generated list of articles. This is extremely helpful because it provides full type safety on the frontmatter. Any article included in the array of article objects content-collections generates is guaranteed to have a valid title, created date, updated date, and description.



The directory option specifies where your files will be stored, and include controls which types of files are processed. In this case, we're only using .mdx files, but if you want to support plain Markdown as well, use **/*.{md,mdx} instead.



Finally, transform processes the article's MDX content and adds an mdx property on the output object, so that we have easy access to the compiled MDX when rendering the post in React.






Create your first blog post



Now that the content-collections infrastructure is in place, it's time to create an article. In the previous step, we configured the articles collection to be stored within the content/articles directory. It's conventional when using content-collections to store all content somewhere within the content directory.



If you haven't already, create a content directory at the root of the project with an articles subdirectory inside. Then add content/articles/test-article.mdx:




CODE
---
title: Chocolate Rooibos Tea is the Best Tea
createdAt: 2026-04-06
updatedAt: 2026-04-06
description: This is a test description.
---

## This is an h2

This is a test article. The content is irrelevant.






This can be named anything, but it should use an .mdx suffix, since we defined the collection to process only .mdx files. Also note that we'll use the file name as the URL slug. If you use a different file name in this step, the slug in later steps will differ accordingly.






Create dynamic article routes



Now that you've created an article, it should show up under .content-collections/generated/allArticles.js as long as your dev server is running and your frontmatter is valid. If you don't see it, try restarting your dev server. However, your TanStack Start app doesn't yet know how to display articles.



To add this functionality, create a file within your routes directory. In this case, we'll call it articles.$slug.tsx. This will allow us to display articles at /articles/<slug>. If you'd prefer to display them directly at /<slug>, you can name the file $slug.tsx instead.



Add the following code:




CODE
import { MDXContent } from '@content-collections/mdx/react';
import { createFileRoute, notFound } from '@tanstack/react-router';
import { allArticles } from 'content-collections';

export const Route = createFileRoute('/articles/$slug')({
component: RouteComponent,
loader: async ({ params: { slug } }) => {
const article = allArticles.find((article) => article._meta.path === slug);
if (!article) throw notFound();
return article;
},
});

function RouteComponent() {
const article = Route.useLoaderData();
return (
<>
<h1>{article.title}</h1>
<p>{article.description}</p>
<MDXContent code={article.mdx} />
</>
);
}






This code is fairly straightforward. In the loader, we use allArticles (which is generated by content-collections using the files stored in content/articles) and find the entry whose path matches the current slug.



This means that if you navigate to /articles/test-article-name on your site, this corresponds to a file at content/articles/test-article-name.mdx. Of course, if no such file exists or the frontmatter is invalid, you'll get a Not Found error.



Within the actual component, we render the title as an h1, the description as a p, and the processed MDX content.



Navigate to /articles/test-article (or the appropriate path if you named your MDX file differently), and you'll now be able to see your post. While it may be a bit bare-bones and unstyled, you can verify that your Markdown is indeed being rendered correctly as HTML. Additionally, you can edit the article MDX file and see changes in real time.






Providing SEO Metadata



While not mandatory, you'll likely want to implement at least some basic SEO on the post page. This is easily done within createFileRoute:




CODE
import { createFileRoute, notFound } from '@tanstack/react-router'
import { allArticles } from 'content-collections'

export const Route = createFileRoute('/articles/$slug')({
component: RouteComponent,
loader: async ({ params: { slug } }) => {
const article = allArticles.find((article) => article._meta.path === slug)
if (!article) throw notFound()
return article
},
head: async ({ loaderData: article }) => {
if (!article) return {}
return {
meta: [
{
title: article.title,
description: article.description,
},
],
}
},
})









Enable prerendering



For optimal performance, you'll want to prerender your TanStack Start site at build time. This is easily done by enabling the prerender option for the tanstackStart plugin in vite.config.ts:




CODE
const config = defineConfig({
...
plugins: [
...
tanstackStart({
prerender: {
enabled: true,
},
}),
...
],
})

export default config









Next steps



After working through this tutorial, you should now have a fully-functional TanStack Start app with all of the core infrastructure in place for creating prerendered blog posts from MDX files. However, there's still plenty of room for improvement.



For a more robust system, you'll probably want to build a styled ArticlePage component or similar that takes an Article as input and renders out the title, description, dates, and any other metadata, as well as the MDX content. Styling your MDX content with Tailwind Typography is another excellent way to quickly improve the visual quality of your posts.



For more complex use cases such as code syntax highlighting, you may want to use remark or rehype plugins in your MDX compilation step. See the content-collections docs for more information on this.



In many cases, your content site may have a variety of different article types that may require slightly different metadata or different categories. For this, you can define a base Zod schema with all core metadata, and then create more specific article schemas by extending this:




CODE
import { defineCollection, defineConfig } from '@content-collections/core'
import { compileMDX } from '@content-collections/mdx'
import { z } from 'zod'

const articleSchema = z.object({
title: z.string(),
createdAt: z.iso.date(),
updatedAt: z.iso.date(),
description: z.string(),
})

const softwareArticleSchema = articleSchema.extend({
categories: z.array(z.enum(['react', 'tailwind', 'tanstackStart'])),
})

const softwareArticles = defineCollection({
name: 'softwareArticles',
directory: 'content/articles/software',
include: '**/*.mdx',
schema: softwareArticleSchema,
transform: async (document, context) => {
const mdx = await compileMDX(context, document)
return { ...document, mdx }
},
})

export default defineConfig({
content: [softwareArticles],
})






The important part is that the core content pipeline is now in place: content-collections gives you typed article data, and TanStack Start turns that data into routes, metadata, and content.

Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Protect Kubernetes Services with OAuth2 Proxy, Gateway API, Traefik, and Pocket ID
1 Quelle
Request lifecycle: HandlerMapping HandlerAdapter resolvers
1 Quelle
The best n8n fix I found this month was boring: lower your agent concurrency settings before touching the prompt
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How to build a blog with TanStack Start and Content Collections

Thematisch verwandte Begriffe: build, blog, with, TanStack · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...