Skip to main content

Technical On-page SEO (for Astro Websites)

Last updated: December 31, 2025

Complete SEO implementation reference for Astro sites. This checklist covers technical architecture, on-page optimization, structured data, local signals, analytics, and coding practices.

What are the key Astro paradigms for SEO?

  • Focus on Islands Architecture. Instead of auditing “deferred loading,” audit your client directives.
  • Use Content Collections to manage the data that populates these schemas.

How do I optimize technical performance?

Critical CSS

  • System font fallbacks
  • <link rel="preload" as="style"> swap technique
  • Budget: ≤14 kB initial CSS

JavaScript optimization

  • Console/debugger stripped in prod
  • Route-level code-splitting
  • Tree-shaking (audit unused bytes)

Image optimization

  • <link rel="preload"> for hero images
  • Next-gen formats (WebP, AVIF)
  • loading="lazy" + decoding="async"
  • srcset & sizes for responsive
  • SVG for icons/logos

Font loading

  • font-display: swap
  • Preload critical fonts
  • <link rel="preconnect"> to font host
  • Subset fonts (latin only)

Build config (astro.config.mjs)

  • compressHTML: true
  • CSS/JS minification
  • inlineStylesheets: 'auto'
  • Sitemap with priorities

Resource hints

  • <link rel="preconnect"> for GTM, fonts
  • <link rel="dns-prefetch"> for external domains
  • Early Hints (HTTP 103) where supported

Core Web Vitals targets

  • LCP < 2.5s
  • CLS < 0.1
  • INP < 200ms
  • Lighthouse CI in GitHub Actions

URL structure

  • Canonical tags on all pages
  • Trailing-slash consistency
  • Lowercase URLs
  • Parameter handling in robots.txt

HTTPS & security

  • TLS 1.3, HSTS
  • No mixed content
  • security.txt (public/.well-known/security.txt)

404 & redirects

  • Custom 404 page (src/pages/404.astro)
  • No redirect chains (max 1 hop)
  • 301 permanent, 302 temporary

Pagination

  • rel="next" / rel="prev" where applicable
  • noindex thin/duplicate facet pages

Crawl budget

  • Monthly log analysis
  • Detect orphan/over-crawled pages
  • Block low-value assets in robots.txt

How do I optimize on-page content and UX?

Clear CTAs

  • Every page has a call to action
  • CTA config centralized in site.ts

Service landing pages

  • Individual page per service
  • Unique copy & FAQs per service
  • Canonical tags
  • ServiceSchema on each

Location landing pages

  • City/area pages with dynamic routing
  • City keywords & landmarks in content
  • LocalBusinessSchema with areaServed
  • Embedded Google Map

Campaign pages

  • UTM-tagged CTAs
  • Scheduled de-index/redirect post-campaign
  • Breadcrumbs (auto-generated)
  • Related services cross-linking
  • Silo structure: services → cities → blog
  • No orphan pages (monthly crawl check)

Mobile optimization

  • Mobile-first responsive CSS
  • Touch-friendly tap targets
  • INP < 200ms goal

Semantic HTML

  • Proper heading hierarchy (h1 → h2 → h3)
  • ARIA labels on interactive elements
  • Skip-to-content link (BaseLayout.astro)
  • Landmark roles

E-E-A-T signals

  • Author bios with credentials
  • Cite authoritative sources
  • ReviewSchema on testimonials

Content freshness

  • Quarterly content review cadence
  • lastmod in sitemaps
  • Auto-remind owners pre-review

How do I implement structured data?

Schema markup (src/components/schemas/)

SchemaPurpose
OrganizationSchemaCompany, logo, social links
WebsiteSchemaSearchAction for sitelinks
BreadcrumbSchemaNavigation trail
ArticleSchemaBlog posts with dates
FAQPageSchemaFAQ rich snippets
LocalBusinessSchemaLocation pages with geo
ServiceSchemaService offerings

Additional fields:

  • lastReviewed / lastModified for freshness
  • Review & Rating where applicable

Meta tags (BaseLayout.astro)

  • Dynamic <title> with fallback
  • Description truncated to 155 chars
  • Canonical URL auto-generated
  • robots meta (max-image-preview:large)
  • Viewport meta
  • hreflang (if multi-region)

Open Graph & Twitter

  • og:type, og:url, og:title, og:description
  • og:image 1200x630
  • article:published_time, article:modified_time
  • Twitter Cards: summary_large_image

XML sitemap

  • @astrojs/sitemap integration
  • Priority by URL pattern (home=1.0, services=0.9)
  • changefreq settings
  • Excludes /pitches/
  • Image/video sitemaps (optional)
  • Nightly diff alerts (optional)

Robots.txt

  • Allow all, 1s crawl-delay
  • Block /pitches/
  • Block bad bots (Ahrefs, Semrush, DotBot, MJ12)
  • Sitemap location
  • Block low-value query params

AI bot access

  • Allow: GPTBot, Claude, ChatGPT, Perplexity
  • Crawl-delay for LLM bots
  • Disallow training on private assets
  • llms.txt with max-tokens

RSS feed

  • /rss.xml for blog posts
  • Auto-generated from content collection

How do I build local and authority signals?

Business information

  • Consistent NAP (Name, Address, Phone)
  • Schema: priceRange, openingHoursSpecification
  • Clickable phone & SMS links
  • Certifications displayed

Local SEO off-site

  • Google Business Profile complete
  • Structured citations
  • Review generation SOP
  • Authority gap analysis vs competitors
  • Outreach calendar
  • Weekly new link tracking

How do I install Google Tag Manager in Astro?

Adding GTM to your Astro site requires placing code in your base layout so it loads on every page.

Step 1: Get your GTM container ID

  1. Go to Google Tag Manager
  2. Create an account or select your container
  3. Copy your Container ID (e.g., GTM-XXXXXXX)

Step 2: Add GTM ID to your site config

Create or update src/config/site.ts:

export const siteConfig = {
  // ... other config
  gtmId: 'GTM-XXXXXXX',
};

Step 3: Update your base layout

In src/layouts/BaseLayout.astro, add the GTM scripts:

---
import { siteConfig } from '../config/site';
const { gtmId } = siteConfig;
---

<html lang="en">
  <head>
    <!-- Google Tag Manager -->
    <script is:inline define:vars={{ gtmId }}>
      (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
      new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
      j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
      'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
      })(window,document,'script','dataLayer', gtmId);
    </script>
    <!-- End Google Tag Manager -->
  </head>
  <body>
    <!-- Google Tag Manager (noscript) -->
    <noscript>
      <iframe
        src={`https://www.googletagmanager.com/ns.html?id=${gtmId}`}
        height="0"
        width="0"
        style="display:none;visibility:hidden">
      </iframe>
    </noscript>
    <!-- End Google Tag Manager (noscript) -->

    <slot />
  </body>
</html>

Step 4: Verify installation

  1. Build and deploy your site
  2. Open GTM and click Preview
  3. Enter your site URL
  4. GTM should show “Tag Assistant Connected”

Performance tip

For better Core Web Vitals, defer GTM loading:

<script is:inline define:vars={{ gtmId }}>
  window.addEventListener('load', function() {
    setTimeout(function() {
      (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
      new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
      j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
      'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
      })(window,document,'script','dataLayer', gtmId);
    }, 100);
  });
</script>

How do I set up analytics and monitoring?

Google Tag Manager

  • GTM ID in site.ts
  • Loaded with noscript fallback
  • Deferred for performance
  • Server-side tagging (optional)
  • Consent Mode v2 — see How do I create a cookie policy?

Google Analytics (GA4)

  • Set up via GTM container
  • Phone call tracking
  • Custom events
  • CWV dashboard

Google Search Console

  • Verify via HTML file or DNS
  • Submit sitemap
  • Monitor indexing issues

Measurement cadence

  • Weekly: CWV & rank tracking
  • Nightly: Sitemap diff alerts
  • Weekly: Broken link & 404 scan
  • Monthly: Schema validation

What coding practices support SEO?

CI/CD pipeline

  • Cloudflare Pages auto-deploy on push
  • Preview deployments for PRs
  • Build caching for faster deploys
  • Environment variables for secrets

Lighthouse CI (.github/workflows/lighthouse.yml)

  • Run Lighthouse in GitHub Actions on push/PR
  • Config in lighthouserc.json
  • Fail build if score < threshold (perf 90%, a11y 90%, SEO 90%)
  • Track performance regressions over time

Code quality

  • TypeScript for type safety
  • ESLint + Prettier for consistency
  • Pre-commit hooks (husky + lint-staged)
  • PR reviews required

Testing

  • Playwright for E2E tests
  • Visual regression testing
  • Broken link checker (.github/workflows/link-check.yml) - weekly + on PR
  • Schema validation tests

Version control

  • Conventional commits for changelog
  • Semantic versioning
  • Protected main branch
  • Squash merge PRs

Content workflow

  • Content collections with Zod schemas
  • MDX for rich content
  • Draft/published flag support
  • YAML frontmatter validation

Image pipeline

  • Cloudflare Images CDN
  • Auto-optimization on upload
  • Responsive variants generated
  • Cache headers configured

Secrets management

  • .env for local dev
  • Cloudflare env vars for prod
  • No secrets in repo
  • Rotate keys quarterly

Dependency management (.github/dependabot.yml)

  • Dependabot for npm + GitHub Actions updates (weekly)
  • Lock file committed
  • Audit for vulnerabilities
  • Minimal dependencies

Documentation

  • CLAUDE.md for AI context
  • README with setup instructions
  • Inline code comments where needed
  • Knowledge base for team

Verification checklist


File structure

.github/
├── dependabot.yml              # Automated dependency updates
├── workflows/
│   ├── lighthouse.yml          # Lighthouse CI (perf/a11y/SEO checks)
│   └── link-check.yml          # Broken link checker (weekly + on PR)
src/
├── config/site.ts              # Metadata, GTM, social links
├── components/schemas/         # JSON-LD structured data
├── layouts/BaseLayout.astro    # Meta tags, OG, GTM, skip-to-content
├── pages/
│   ├── 404.astro               # Custom 404 page
│   └── rss.xml.js              # RSS feed
public/
├── .well-known/security.txt    # Security contact info
├── robots.txt
├── manifest.json
├── favicon.ico/svg/png
lighthouserc.json               # Lighthouse CI config
astro.config.mjs                # Sitemap, build config

What does Astro already handle?

SuggestionHow Astro Handles It
Deferred loading (defer attr)Scripts are deferred by default
Minification via TerserBuilt-in HTML/JS minification
Inline above-the-fold stylesinlineStylesheets: 'auto' in astro.config.mjs

Future steps

  • Add CI/CD step to push up public/images to Cloudflare Images (if they’re not there)

Looking for expert guidance? Schedule a free consult:

Book a Free Consultation