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.
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
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:
npm create astro@latest my-smb-site# Choose: "Empty" template → Yes to TypeScript → Yes to install dependenciescd my-smb-sitenpm run devThe 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 configurationConfigure astro.config.mjs
Enable SSG mode and declare the site URL so the sitemap and canonical tags work correctly:
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.
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
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
---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', },});Step 3: Optimize Performance and Local SEO with Astro
Dynamic meta tags
Create a reusable SEO.astro component to include in all layouts:
---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.
pnpm add @astrojs/sitemapSelf-hosted fonts with @fontsource
Google Fonts adds an external network request that hurts LCP. Replace it with self-hosted fonts:
pnpm add @fontsource/inter---// In the main layoutimport '@fontsource/inter/400.css';import '@fontsource/inter/700.css';---Measuring the gains
Here are typical results after migrating a WordPress site to Astro:
| Metric | WordPress (before) | Astro (after) |
|---|---|---|
| LCP | 4.2s | 0.9s |
| CLS | 0.18 | 0.01 |
| Lighthouse Performance Score | 52 | 97 |
| Page size (HTML + JS) | 380 KB | 28 KB |
| Time to First Byte | 820ms | 45ms |
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:
- New Resource → Application
- Select the
my-smb-siteGitHub repository - Choose the Static Site type (Coolify detects Astro automatically)
Configure the build
Build Command : npm run buildPublish Directory: distNode Version : 18Environment variables
If the site calls external APIs (contact form, analytics), declare the variables in Coolify’s Environment tab:
PUBLIC_SITE_URL=https://my-smb-site.comPUBLIC_FORMSPARK_ID=xxxxxxxxxxxxPUBLIC_ 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.comTest 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:
| Solution | Price | Integration |
|---|---|---|
| Formspark | €9/month (1,000 submissions) | HTML action attribute |
| Netlify Forms | Free up to 100/month | HTML netlify attribute |
Create the ContactForm.astro component
---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: Webhook → Slack (or Send Email)
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.