Written by Rodrigo de Miguel
Preparing an Astro blog with good judgment (2/5)
(SSG, sitemap, and not overcomplicating things)
How to prepare an Astro blog starting from the official template, making small tweaks to keep it cleaner and more consistent: static HTML, sitemap, and consistent URLs—without unnecessary complexity or infra.
Context: we’re not building anything big (and that’s totally fine)
This post is the second in the series, and it’s worth leaving something clear right from the start.
We’re not building:
- a SaaS
- a platform
- or a complex system
We’re starting from the Astro blog template and making a couple of small, conscious tweaks—exactly the same ones I applied to this personal portfolio—to leave it tidy, predictable, and easier to reason about. Plain, SEO-friendly basics.
Nothing fancy.
The idea is simple:
- know what each piece is doing
- avoid accepting configs “because that’s how the template comes”
- and leave the project in a comfortable place to keep shipping without friction
What we’re aiming for in this post
By the end of this article we should have:
- An Astro blog running
- Static HTML generated (SSG)
- Consistent URLs
- Automatic sitemap
- Everything tested locally
This is not “final production”.
It’s a solid starting point: clean, boring in the good sense, and with no surprises waiting for you later.
Step 1 — Create the project using the blog template
We’ll move quickly here, because Astro already nails this part and there’s plenty of material out there explaining the basics.
npm create astro@latestDuring the wizard:
- Choose the blog template
- TypeScript: yes (recommended)
- Extra frameworks: not needed for now
- Install dependencies: yes
At this point you already get:
- pages
- Markdown posts
- basic navigation
All the essentials are there.
Quick sanity check
npm run devOpen the browser and check that:
- the blog loads
- you can open a post
- nothing explodes in the console
If everything looks fine, we keep going.
Step 2 — What kind of blog are we building (SSG)
Before touching any config, it’s worth stopping for a moment.
This blog:
- has no backend
- has no dynamic data
- doesn’t personalize content
That puts it squarely in Static Site Generation territory.
SSG simply means:
generate the HTML once and serve it as-is
For a blog or a portfolio, this is usually the most straightforward and sensible path. Fewer moving parts, fewer things to break, fewer headaches later.
Step 3 — Make it explicit that we use SSG
Astro uses SSG by default, but I like to make it explicit. Not because it’s required, but for mental clarity and future-you sanity.
// astro.config.mjs
import { defineConfig } from 'astro/config'
export default defineConfig({
output: 'static',
})This doesn’t change behavior, but when you come back to this project months later, you instantly know what kind of site you’re dealing with.
Test the build
npm run buildIf you see a dist/ folder full of HTML, you’re on the right track.
Quick note: Make sure
dist/is in your.gitignore. It’s generated content and shouldn’t be committed to the repo.
Step 4 — A small but important detail: URLs
This is one of those choices that feels trivial at first and painful if you ignore it.
I’ve been burned by this in other projects: duplicated URLs, canonical messes, subtle SEO issues that are annoying to clean up later.
Some platforms force you to make this decision (e.g., WordPress). Here, you can choose. Personally, I prefer not to have slashes at the end.
Google prefers slashes. However, the important thing is not preference, but consistency.
For this case we’ll use:
trailingSlash: 'always'Which means:
/contact/is the final URL- it generates
/contact/index.html
It’s not “better” than other options. It’s just coherent, and search engines care a lot about that.
Later on, with CloudFront Functions, we’ll make sure users never have to type index.html and everything resolves cleanly from S3. If you skip this step, the site starts to feel oddly retro.
While you’re using the Astro server (or npm run preview), URLs look clean by default. Once you move to S3 + CloudFront, you’re serving static files, so you need to be intentional about how those files map to URLs.
Make sure internal links follow the same pattern (with trailing slash). Astro will complain if they don’t, which is actually helpful.
Full example:
export default defineConfig({
output: 'static', // default value, Static Site Generation
trailingSlash: 'always', // Manage trailing slashes in URLs for SEO and serve HTML from CloudFront. We will discuss this later.
site: SEO_BASE_URL, // The base URL of your website for sitemap, canonical URLs, internal linking, etc. Should be 'https://yourdomain.com'
})Note: Google doesn’t care if it’s
alwaysornever. It cares that you pick one and stick to it. Mixing both is where problems start.
Step 5 — Add a sitemap
Google can crawl your site without a sitemap, but having one is still good hygiene. You’re giving hints about structure, priorities, and change frequency.
Astro keeps this simple with @astrojs/sitemap. The template includes it, but here’s how to wire it manually.
Install
npm install @astrojs/sitemapSimple configuration
import sitemap from '@astrojs/sitemap'
export default defineConfig({
output: 'static',
trailingSlash: 'always',
site: SEO_BASE_URL,
integrations: [
sitemap({
changefreq: 'weekly',
priority: 1, // Google is king, and the truth is that it ignores this.
lastmod: new Date(), // Enough to get started
i18n: { // If you have multiple languages
defaultLocale: 'es',
locales: {
es: 'es-ES',
en: 'en-US',
},
},
}),
],
})Is it perfect? No. Is it good enough to start? Yes.
And that’s the point here.
Step 6 — Test everything locally, calmly
Before moving on, run:
npm run build
npm run previewCheck that:
- navigation works
- URLs look right
/sitemap.xmlexists
No need to overthink it. Just confirm nothing weird is happening.
Things we are NOT going to do
For this tutorial we’re skipping:
- a CMS
- databases
- SSR
- heavy analytics
- extra plugins “just in case”
Not because those things are wrong, but because they’re not needed here.
Fewer moving parts means:
- fewer decisions
- less maintenance
- less mental noise
Quick checklist before moving forward
- The blog works with the base template
- The build generates HTML
- URLs are consistent
- The sitemap is generated
- Everything is tested locally
If all of this checks out, you already have a very solid foundation.
What we actually did
If you zoom out, nothing here is flashy:
- Use the official template
- Make SSG explicit
- Define URL behavior
- Add a sitemap
But the result is:
- a cleaner structure
- easier long-term maintenance
- a blog that’s more SEO-friendly from day one
Small steps, good judgment.
You don’t need complexity to do things properly.
A handful of deliberate decisions early on save you a lot of friction later.
That’s what this series is about.
We're going to serve it properly.
Posts in this series
1️⃣ Part 1: A blog shouldn’t be a SaaS
👉 Part 2: Preparing an Astro blog with good judgment
3️⃣ Part 3: S3 + CloudFront to serve a fast and cheap static blog
4️⃣ Part 4: Custom domain with Route 53 and CloudFront for an Astro SSG blog
5️⃣ Part 5: Automate Astro SSG blog deployment with GitHub Actions and AWS
🐙 GitHub Repo: demo-astro-ssg-s3-cloudfront↗