Skip to main content
Web Development February 20, 2026 14 min read

Tutorial: Migrating Your Site to Astro to Boost SMB Performance

Migrate your site to Astro in 2026 to improve Core Web Vitals, cut hosting costs, and boost local SEO. A complete step-by-step guide for small businesses.

M
Mohamed Boukri

Why Astro Is the Right Choice for SMBs in 2026

If your WordPress site is slow, expensive to host, and slipping in Google rankings, this tutorial is for you.

Astro is a Static Site Generator (SSG) that produces ultra-lightweight HTML pages. Unlike Next.js — which ships JavaScript on every request — or WordPress — which queries a database on every visit — Astro delivers pure HTML to the browser. The result: load times cut by two to five times, Core Web Vitals in the green, and local SEO that improves naturally.

This guide is aimed at SMB owners who have an existing brochure site or blog and no dedicated dev team. By the end, you’ll have migrated your content, set up automatic deployment on Coolify, and added a contact form — with no backend required.

Key Takeaway
Astro generates static HTML by default: zero unnecessary JavaScript sent to the browser, which translates directly into better performance and better search rankings.

Prerequisites: What You Need Before You Start

Before diving in, make sure the following are in place:

  • Node.js 18+ installed locally — pnpm is recommended as a package manager for its speed
  • A Git repository on GitHub or GitLab with write access
  • Basic HTML/CSS knowledge — no React or Vue experience required
  • A Coolify account (self-hosted) or a Netlify account for continuous deployment
Coolify is an open-source alternative to Netlify that you can host on a VPS for around €5/month. It’s often more cost-effective for SMBs that already have a server.

Step 1: Initialize an Astro Project and Understand Its Structure

Create the project

The simplest way to get started is to use the official CLI:

Terminal window
npm create astro@latest my-smb-site
# Choose: "Empty" template → Yes to TypeScript → Yes to install dependencies
cd my-smb-site
npm run dev

The dev server starts at http://localhost:4321. You’ll see a blank page — that’s expected, since you’re starting from scratch.

Understand the folder structure

my-smb-site/
├── src/
│ ├── pages/ ← Each file = one URL
│ ├── components/ ← Reusable components (.astro)
│ ├── layouts/ ← Page templates
│ └── content/ ← Content collections (blog, etc.)
├── public/ ← Static assets (images, favicon)
└── astro.config.mjs ← Central configuration

Configure astro.config.mjs

Enable SSG mode and declare the site URL so the sitemap and canonical tags work correctly:

astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
output: 'static',
site: 'https://my-smb-site.com',
integrations: [sitemap()],
});

Island Architecture in a nutshell

Astro ships static HTML by default. If part of a page needs interactivity — a hamburger menu, a form with live validation — you isolate it in an “island” using the client:load directive. The rest of the page stays pure HTML. That’s what drives the performance gains: JavaScript only loads where it’s strictly needed.

For a typical SMB brochure site, you often don’t need any islands at all. HTML and CSS cover 90% of what you need.

Step 2: Migrate Your Existing Content to Astro

Export from WordPress

Use the free WP Markdown Exporter plugin to convert your posts to .md files. After exporting, you get a ZIP archive with one file per post, frontmatter included.

---
title: "How to Choose an Accountant for Your Small Business"
date: "2024-11-15"
description: "A practical guide for SMB owners."
---
Article content...

Place these files in src/content/blog/ — not directly in src/pages/. Astro’s Content Collections give you more flexibility.

Configure Content Collections

src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.coerce.date(),
description: z.string(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };

Create a reusable layout

src/layouts/BlogLayout.astro
---
const { title, description, date } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
<title>{title} — My SMB Site</title>
</head>
<body>
<header><!-- Navigation --></header>
<main>
<h1>{title}</h1>
<time>{date.toLocaleDateString('en-GB')}</time>
<slot />
</main>
<footer><!-- Footer --></footer>
</body>
</html>

Handle images

Astro includes a built-in <Image /> component that automatically converts images to WebP and generates width/height attributes to prevent layout shift (CLS):

---
import { Image } from 'astro:assets';
import teamPhoto from '../assets/team-photo.jpg';
---
<Image src={teamPhoto} alt="The team" width={800} height={450} />

Preserve existing URLs

This is the most critical step for SEO. If your WordPress posts lived at /blog/my-post/, create the exact same path in Astro. If any URL must change, configure redirects in astro.config.mjs:

export default defineConfig({
redirects: {
'/old-url': '/new-url',
},
});
Never change URLs without setting up 301 redirects. You’ll lose the SEO authority built up on your existing pages.

Step 3: Optimize Performance and Local SEO with Astro

Dynamic meta tags

Create a reusable SEO.astro component to include in all layouts:

src/components/SEO.astro
---
const {
title,
description,
ogImage = '/og-default.jpg',
canonicalURL = Astro.url,
} = Astro.props;
---
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage} />

Automatic sitemap

The @astrojs/sitemap integration (added in Step 1) generates sitemap-index.xml automatically on every build. All you need to do is submit it in Google Search Console.

Terminal window
pnpm add @astrojs/sitemap

Self-hosted fonts with @fontsource

Google Fonts adds an external network request that hurts LCP. Replace it with self-hosted fonts:

Terminal window
pnpm add @fontsource/inter
---
// In the main layout
import '@fontsource/inter/400.css';
import '@fontsource/inter/700.css';
---

Measuring the gains

Here are typical results after migrating a WordPress site to Astro:

MetricWordPress (before)Astro (after)
LCP4.2s0.9s
CLS0.180.01
Lighthouse Performance Score5297
Page size (HTML + JS)380 KB28 KB
Time to First Byte820ms45ms
Always measure in private browsing mode on mobile with a simulated slow 4G connection — that gives you numbers representative of your real users.

Step 4: Deploy the Astro Site on Coolify with Automated CI/CD

Connect the repository to Coolify

In the Coolify interface, create a new resource:

  1. New ResourceApplication
  2. Select the my-smb-site GitHub repository
  3. Choose the Static Site type (Coolify detects Astro automatically)

Configure the build

Build Command : npm run build
Publish Directory: dist
Node Version : 18

Environment variables

If the site calls external APIs (contact form, analytics), declare the variables in Coolify’s Environment tab:

Terminal window
PUBLIC_SITE_URL=https://my-smb-site.com
PUBLIC_FORMSPARK_ID=xxxxxxxxxxxx
Variables prefixed with PUBLIC_ are exposed on the client side in Astro. Variables without that prefix stay server-side (build time only).

Enable automatic deployment

In the Coolify application settings:

  • Auto Deploy: enabled
  • Branch: main
  • Copy the Webhook URL generated by Coolify
  • In GitHub → Settings → Webhooks → paste the URL

From this point on, every git push to main triggers a build and automatic deployment. The whole process takes around 45 seconds for a typical SMB-sized site.

Verify the deployment

In Coolify’s Deployments tab, you can follow the logs in real time:

✓ Cloning repository...
✓ Installing dependencies (pnpm install)
✓ Building (npm run build)
> astro build
✓ 47 pages built in 3.2s
✓ Deploying to /dist
✓ Site live at https://my-smb-site.com

Test with the custom domain and confirm the SSL certificate is active — Coolify handles Let’s Encrypt automatically.


Step 5: Going Further — Adding a Contact Form Without a Backend

Choose a form solution

Two options that require no dedicated Node.js server:

SolutionPriceIntegration
Formspark€9/month (1,000 submissions)HTML action attribute
Netlify FormsFree up to 100/monthHTML netlify attribute

Create the ContactForm.astro component

src/components/ContactForm.astro
---
const FORMSPARK_ID = import.meta.env.PUBLIC_FORMSPARK_ID;
---
<form
action={`https://submit-form.com/${FORMSPARK_ID}`}
method="POST"
class="contact-form"
>
<input type="hidden" name="_redirect" value="/thank-you" />
<label for="name">Name *</label>
<input
type="text"
id="name"
name="name"
required
minlength="2"
maxlength="100"
/>
<label for="email">Email *</label>
<input
type="email"
id="email"
name="email"
required
/>
<label for="message">Message *</label>
<textarea
id="message"
name="message"
required
minlength="10"
rows="5"
></textarea>
<button type="submit">Send</button>
</form>

Native HTML5 validation (required, type="email", minlength) covers 95% of use cases without a single line of JavaScript.

When to use an Astro island

If you want real-time validation — showing errors as the user types — turn the component into an island:

<!-- In contact.astro -->
<InteractiveContactForm client:load />

Otherwise, keep the component as a plain .astro file — no client:load, no JavaScript shipped.

Connect notifications to n8n

Formspark supports webhooks. Configure a webhook pointing to n8n to trigger a Slack notification or email on every submission:

  • In Formspark → Settings → Webhooks → add the n8n webhook URL
  • In n8n, create a workflow: WebhookSlack (or Send Email)
n8n can also filter spam by checking whether the submitted email appears on a blocklist before sending the notification.

Conclusion: A Worthwhile Migration and What Comes Next

Following this tutorial, you’ve gone from a slow, costly WordPress site to a static Astro site with near-perfect Lighthouse scores, stronger local SEO, and hosting costs cut by three to five times.

The natural next step for an SMB is to connect a headless CMS — Decap CMS or Sanity — so your team can publish content without touching the code. That’s the power of JAMstack: content stays editable, the site stays static and fast.

In a future article, we’ll explore how to automate publishing new posts via n8n: write in Notion, approve, and trigger an Astro deployment automatically — no technical intervention needed.

Need help migrating your site? At Kodixar, I help SMBs through exactly this kind of transition. Get in touch to talk it through.

Available for new projects

Need help with this topic?

Contact us to discuss your project and see how we can help.

Free quote
No commitment
24h response