Blog
The blog uses Astro’s content collections - Markdown files in src/content/blog/, typed with a Zod schema.
src/content.config.ts
Section titled “src/content.config.ts”import { defineCollection, z } from 'astro:content';
const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), description: z.string(), pubDate: z.date(), tags: z.array(z.string()).default([]), }),});
export const collections = { blog };Post frontmatter
Section titled “Post frontmatter”---title: "Why local businesses need a website in 2025"description: "Most small businesses still rely on word of mouth. Here's why that's leaving money on the table."pubDate: 2025-06-01tags: ["local-business", "website", "seo"]---
Post content starts here...File naming
Section titled “File naming”The filename becomes the URL slug:
src/content/blog/why-local-business-needs-a-website.md-> /blog/why-local-business-needs-a-website/Use kebab-case, no spaces, no uppercase.
Blog index page (src/pages/blog/index.astro)
Section titled “Blog index page (src/pages/blog/index.astro)”---import { getCollection } from 'astro:content';const posts = (await getCollection('blog')).sort( (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());---Sort by pubDate descending (newest first).
Blog post page (src/pages/blog/[slug].astro)
Section titled “Blog post page (src/pages/blog/[slug].astro)”---import { getCollection, render } from 'astro:content';
export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(p => ({ params: { slug: p.slug }, props: { post: p } }));}
const { post } = Astro.props;const { Content } = await render(post);---
<Layout title={post.data.title} description={post.data.description}> <article class="prose prose-invert max-w-none"> <Content /> </article></Layout>Tag pages live at src/pages/tags/[tag].astro. Each tag becomes a browsable archive page.
RSS feed
Section titled “RSS feed”src/pages/rss.xml.ts generates an RSS feed at /rss.xml. The blog index links to it. This is already set up in the Juju Alpha template.
SEO for blog posts
Section titled “SEO for blog posts”- Each post gets its own
titleanddescriptionvia frontmatter - The
[slug].astropage passes these to<Layout> - Add
FAQPageorHowToJSON-LD to posts where relevant (pass via<slot name="head">) - Blog posts are included in the sitemap automatically via
@astrojs/sitemap
Content tips
Section titled “Content tips”- Write for the client’s target customer, not for other developers
- Target one keyword per post (include in title, first paragraph, at least one H2)
- 800-1500 words is the sweet spot for service business blogs
- Link internally to service pages where relevant
- End every post with a CTA linking to
/contact