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.
In This Guide
Islands Architecture and Content Collections for SEO
How do I optimize technical performance?CSS, JavaScript, images, fonts, and Core Web Vitals
How do I optimize on-page content and UX?CTAs, landing pages, navigation, and semantic HTML
How do I implement structured data?Schema markup, meta tags, sitemaps, and robots.txt
How do I build local and authority signals?NAP consistency, local SEO, and backlinks
How do I install Google Tag Manager in Astro?Add GTM to every page in your Astro site
How do I set up analytics and monitoring?GTM, GA4, Search Console, and measurement cadence
What coding practices support SEO?CI/CD, Lighthouse CI, testing, and documentation
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&sizesfor 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 applicablenoindexthin/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
Navigation & internal linking
- 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
lastmodin sitemaps- Auto-remind owners pre-review
How do I implement structured data?
Schema markup (src/components/schemas/)
| Schema | Purpose |
|---|---|
| OrganizationSchema | Company, logo, social links |
| WebsiteSchema | SearchAction for sitelinks |
| BreadcrumbSchema | Navigation trail |
| ArticleSchema | Blog posts with dates |
| FAQPageSchema | FAQ rich snippets |
| LocalBusinessSchema | Location pages with geo |
| ServiceSchema | Service offerings |
Additional fields:
lastReviewed/lastModifiedfor freshness- Review & Rating where applicable
Meta tags (BaseLayout.astro)
- Dynamic
<title>with fallback - Description truncated to 155 chars
- Canonical URL auto-generated
robotsmeta (max-image-preview:large)- Viewport meta
hreflang(if multi-region)
Open Graph & Twitter
og:type,og:url,og:title,og:descriptionog:image1200x630article:published_time,article:modified_time- Twitter Cards:
summary_large_image
XML sitemap
@astrojs/sitemapintegration- Priority by URL pattern (home=1.0, services=0.9)
changefreqsettings- 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.txtwith max-tokens
RSS feed
/rss.xmlfor 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
Backlinks & PR
- 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
- Go to Google Tag Manager
- Create an account or select your container
- 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
- Build and deploy your site
- Open GTM and click Preview
- Enter your site URL
- 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
.envfor 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.mdfor AI context- README with setup instructions
- Inline code comments where needed
- Knowledge base for team
Verification checklist
- Google Search Console verified
- Sitemap submitted
- GTM container connected
- Rich Results Test
- Facebook Debugger
- Twitter Card Validator
- PageSpeed Insights > 90
- Core Web Vitals passing
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?
| Suggestion | How Astro Handles It |
|---|---|
Deferred loading (defer attr) | Scripts are deferred by default |
| Minification via Terser | Built-in HTML/JS minification |
| Inline above-the-fold styles | inlineStylesheets: '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