A blog is a strong AI project — conventional structure, verifiable by looking, no meaningful security surface if you’re the only author.
It’s also the project where AI’s blind spot is most costly, because a blog exists to be found, and everything that makes it findable is invisible on the page.
Pick Your Content Source First
This decision shapes everything else.
Markdown in the repo — simplest and fastest. Posts are files, version controlled, no database, no CMS costs. Right answer if the writers are comfortable with Git.
A headless CMS — Sanity, Payload, or Contentful. Necessary when non-technical people need to publish without touching a repo.
Database-backed with your own editor — avoid this. Building a decent editing experience is far more work than it appears, and the result is always worse than an off-the-shelf one.
This guide uses markdown. Swap the source if you need a CMS; everything else holds.
The Build
Astro with TypeScript and Tailwind. A blog using content collections with markdown files.
Frontmatter schema: title, description, pubDate, updatedDate optional, category, tags array, heroImage optional, author, draft boolean.
Routes: / for the post index paginated at 12 per page, /[slug]/ for posts, /category/[category]/ and /tag/[tag]/ for filtering.
Statically generate everything at build time. Exclude drafts from production builds.
Astro is a good fit here specifically because it ships zero JavaScript by default, which makes blogs fast without effort.
The SEO Layer — Ask For All Of It
This is the entire point of the project and none of it will be there unless you name it.
Add complete SEO to every post page:
- Title tag under 60 characters, meta description under 155
- Self-referencing canonical URL
- Open Graph and Twitter card tags with the hero image
- JSON-LD BlogPosting schema with headline, description, image, datePublished, dateModified, author and publisher
- JSON-LD BreadcrumbList
- Exactly one h1 per page, no skipped heading levels
- Descriptive alt text on every image
Site-wide: Organization and WebSite JSON-LD, an XML sitemap, an RSS feed, and robots.txt pointing at the sitemap.
Then verify rather than trusting it:
curl -s https://yoursite.com/some-post/ | grep -o 'application/ld+json' | wc -l
Paste a post URL into Google’s Rich Results Test. If the structured data doesn’t validate there, it doesn’t exist as far as Google is concerned.
Don’t Load Everything on the Index
The default implementation reads every post’s full content to render a list of excerpts. Fine at 10 posts. Noticeably slow at 300.
The index and category pages must read only frontmatter, never full post bodies. Excerpts come from the description field, not from truncating content.
Images
Images are where blog performance goes to die.
Use Astro’s Image component for all images. Serve WebP with fallbacks, set explicit width and height to prevent layout shift, lazy-load everything below the fold, and eager-load the hero.
Layout shift from images without dimensions is both a ranking factor and genuinely irritating.
Internal Linking
Worth building in, because it’s the highest-leverage SEO work most blogs never do.
Add a related posts component showing 3 posts from the same category, excluding the current one, falling back to recent posts if there aren’t enough. Add previous/next navigation within the category.
Then check your own work:
Write a script that reads all markdown files, extracts internal links, and reports: posts with fewer than 2 internal links, posts with no inbound links from other posts, and any links to slugs that don’t exist.
Orphan posts — pages nothing links to — get crawled less and rank worse. This script finds them in seconds.
Adding a CMS Later
If non-technical writers arrive:
Add Sanity as the content source. Keep the same frontmatter shape so existing markdown still works. Set up a webhook that triggers a rebuild on publish. Add preview mode for drafts.
Keeping the schema identical means you can move without rewriting the site.
The Checklist
- Statically generated, drafts excluded from production
- Meta title and description on every page
- Canonical URLs, self-referencing
- Open Graph image renders correctly (test a real share)
- BlogPosting and BreadcrumbList JSON-LD, validated in Rich Results Test
- Organization and WebSite schema site-wide
- XML sitemap generated and in robots.txt
- RSS feed working
- One h1 per page, hierarchy intact
- Alt text on all images
- Index pages read frontmatter only
- Images optimised with explicit dimensions
- No orphan posts
- PageSpeed above 90 on mobile
The Thing Worth Remembering
Everything on that checklist is invisible. The blog looks finished without a single item done — which is exactly why AI skips them and why most AI-built blogs get no traffic.
A beautiful blog with no structured data, no sitemap, and no internal linking is a blog nobody finds. Ask for the invisible parts explicitly, then verify them with tools rather than by looking.
Related reading: