Template Documentation
Introduction
Everything you need to know before you open your editor — what’s in the box, what it’s built with, and what it expects from your machine.
Welcome
Thanks for picking up SeoGuru, an Astro template for SEO agencies, PPC/paid-media shops, and other marketing consultancies. This guide walks through installing the template, understanding how the project is organized, customizing content and styling, and shipping it to production. It’s written for developers who are comfortable with the command line but new to this specific codebase — if you already know Astro well, feel free to skip ahead using the sidebar.
What’s Included
Source files
The full, uncompiled project source — components, pages, styles, and configuration.
Production build
A pre-built dist/ folder you can deploy as-is if you don’t plan to customize anything.
Design assets
No separate Figma source file is bundled. Stock photography (Unsplash, Pexels), a 3D illustration pack, and the three bundled font families are credited with license links in CREDITS.md at the project root.
This documentation
A single offline HTML file — open it directly in any browser, no server required.
Features
- A homepage, about page, three service pages, a contact page, a pricing page, and a privacy policy page — ready to relabel for your own agency.
- A case-study blog built on Astro content collections, with automatic list, category archive, and tag archive pages generated from Markdown files.
- No UI framework required — every interactive piece (mobile nav, dropdowns, tabs, accordions, the client-logo marquee) is a small vanilla-JS controller, paired with plain CSS.
- GSAP-powered scroll animations layered on top of the same vanilla-JS approach.
- An auto-generated XML sitemap via
@astrojs/sitemap, plus per-page SEO meta tags and Open Graph/Twitter card support.
Technology Stack
SeoGuru is plain Astro — no UI framework, and no CSS framework or preprocessor:
Requirements
| Tool | Minimum version | Notes |
|---|---|---|
| Node.js | 22.12.0 or later | Set in package.json’s engines field; LTS releases are recommended |
| Package manager | npm (bundled with Node.js) | The project ships a package-lock.json; pnpm or Yarn will also work if you prefer |
| Code editor | — | VS Code with the official Astro extension is a comfortable default |
| Git | 2.x | Only required if you plan to version-control your customized copy |
Browser Support
The built site targets the last two versions of all evergreen browsers:
Internet Explorer 11 is not supported. No additional polyfills are bundled.
Getting Started
From a downloaded ZIP to a running dev server in five steps.
Installation & Extraction
- Download the template ZIP from your purchase confirmation email or account downloads page.
- Extract it to a folder on your machine — avoid folder names with spaces or special characters.
- Open that folder in your terminal and in your code editor.
# Extract on macOS / Linux
unzip seoguru-astro.zip -d seoguru-astro
cd seoguru-astro
Node.js Setup
Check which version of Node.js is currently active:
node --version
# v22.12.0 or later
If you manage multiple Node versions, tools like nvm or fnm make switching painless:
nvm install 22
nvm use 22
Install Dependencies
From the project root, install packages with your preferred package manager:
npm install
pnpm install
yarn install
This reads package.json and downloads every dependency into a local node_modules/ folder. It only needs to be re-run when dependencies change.
Run the Development Server
npm run dev
pnpm dev
This starts Astro’s local dev server with hot module reloading — edits to components, pages, and styles appear in the browser immediately without a manual refresh. By default it’s available at http://localhost:4321; the terminal output will confirm the exact address. The project’s own CLAUDE.md also documents an astro dev --background mode with astro dev stop / astro dev status / astro dev logs companion commands, if you’re working with an AI coding agent.
Build for Production & Preview
# Compile an optimized production build into dist/
npm run build
# Serve that build locally to sanity-check it
npm run preview
build type-checks the project, bundles and minifies assets, and writes static output to dist/. preview serves that exact output locally so you can verify it before deploying — it does not rebuild automatically, so re-run build after further changes.
Use dev while you’re customizing the template. Use build, then preview, once before you deploy — it catches issues (like broken TypeScript types or missing imports) that the dev server tolerates but production won’t.
Project Structure
A tour of the folders you’ll actually touch, and the ones you generally shouldn’t.
| Path | Purpose |
|---|---|
public/ | Fonts, images, videos, and the favicon — copied to the output root untouched, no optimization or hashing. Referenced from markup with root-relative paths like /images/Logo.svg. |
src/components/ | Small, reusable pieces of UI (Header, Footer, ServicePage, CaseStudyCard, PricingSection, and others) shared across multiple pages. |
src/content/case-studies/ | One Markdown file per case study, validated against the schema in content.config.js. See Dynamic Routes. |
src/data/ | The faqs.js array of question/answer pairs, shared by the homepage and pricing page FAQ sections. |
src/layouts/ | Layout.astro — the single shared page shell: <head> tags, global stylesheet imports, and the site.js/animations.js script tags. |
src/pages/ | Every file here becomes a route automatically. See Pages for details. |
src/scripts/ | site.js (mobile nav, dropdowns, tabs, accordions) and animations.js (GSAP scroll animations) — loaded as plain <script> tags, no bundler-managed imports required. |
src/styles/ | Plain CSS, no preprocessor: normalize.css, framework.css (Webflow-derived base styles), seoguru.css (this template’s design), and custom.css (post-conversion additions). See Styling. |
content.config.js defines the case-studies content collection’s schema (title, images, category, tags, client details, and so on) using Astro’s content layer API. CREDITS.md lists third-party photo, illustration, and font attribution; CHANGELOG.md holds the version history.
Configuration
The files that control how the project builds, and where to set site-wide values.
package.json
Defines dependencies and the scripts you run from the terminal (dev, build, preview). Add your own script here if you introduce a new tool.
{
"name": "seoguru-astro",
"version": "0.0.1",
"engines": { "node": ">=22.12.0" },
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
}
}
astro.config.mjs
Controls integrations, the deployed site URL, and build behavior. SeoGuru registers a single integration — the official sitemap generator:
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com', // replace with your real deployed URL before launch
integrations: [sitemap()],
});
The site value still reads the placeholder https://example.com. It feeds the sitemap and the canonical/Open Graph URLs built in Layout.astro, so update it to your real deployed domain before you ship — see Production Checklist.
tsconfig.json
SeoGuru doesn’t use TypeScript as a project-wide choice — there are no .ts/.tsx files, and only one component (PricingSection.astro) declares an optional interface Props. This file just extends Astro’s strict preset, which gives you editor type-checking on .astro frontmatter; there are no path aliases configured:
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}
Environment Variables (.env)
SeoGuru doesn’t use any environment variables out of the box — there’s no .env file, and nothing in src/ reads import.meta.env. The site URL, form endpoints, and contact details are plain hard-coded values (see Production Checklist for where to change them). If you add your own API integration later, Astro’s standard convention still applies: create a .env file in the project root, and prefix any value that needs to be readable in client-side code with PUBLIC_.
# Exposed to the browser — never put secrets here
PUBLIC_SITE_URL=https://example.com
# Server-only — safe for secrets, not readable in browser code
API_SECRET_KEY=your_secret_key
If you introduce one, add .env to .gitignore (it usually is by default for Astro projects). Anything without the PUBLIC_ prefix should stay off the client entirely.
Pages
How routes, dynamic case-study pages, and the shared layout fit together.
Creating a New Page
Astro uses file-based routing: any .astro file placed in src/pages/ automatically becomes a route matching its file path. SeoGuru’s own routes:
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/contact.astro → /contact
src/pages/pricing.astro → /pricing
src/pages/privacy-policy.astro → /privacy-policy
src/pages/services/seo-search-engine-optimization.astro → /services/seo-search-engine-optimization
src/pages/case-studies/[...page].astro → /case-studies, /case-studies/2, … (paginated)
src/pages/case-studies/[slug].astro → /case-studies/:slug (dynamic)
To add a page, create a new file in src/pages/ using the layout and structure of an existing, similar page as a starting point:
---
import Layout from '../layouts/Layout.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
---
<Layout title="Services" description="...">
<Header current="services-seo" />
<h1>Our Services</h1>
<Footer />
</Layout>
Editing an Existing Page
Open the relevant file under src/pages/ and edit the markup directly. Anything imported from src/components/ lives elsewhere — edit the component file itself to change content that repeats across multiple pages (like a shared call-to-action block).
Dynamic Routes
Files wrapped in square brackets, such as [slug].astro, generate multiple pages from a single template. SeoGuru uses this for case studies, reading from the case-studies content collection (see Content Editing) rather than a raw glob import:
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('case-studies');
return posts.map((post) => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { data } = post;
---
The case-study list page uses the same collection with Astro’s built-in paginate() helper to generate /case-studies, /case-studies/2, and so on, six posts per page:
---
export async function getStaticPaths({ paginate }) {
const posts = (await getCollection('case-studies')).sort((a, b) => a.data.order - b.data.order);
return paginate(posts, { pageSize: 6 });
}
---
The same pattern generates the category and tag archive pages, at src/pages/case-studies/category/[category].astro and src/pages/case-studies/tag/[tag].astro.
Layouts
SeoGuru has a single shared layout, src/layouts/Layout.astro, that wraps every page’s <html> shell, <head> metadata, global stylesheet imports, and the site.js/animations.js script tags. Header and Footer are imported separately into each page rather than baked into the layout, so pages control their own header state (see Header Navigation):
---
import '../styles/normalize.css';
import '../styles/framework.css';
import '../styles/seoguru.css';
import '../styles/custom.css';
const { title, description, ogImage = '/images/Hero-Image-2.webp' } = Astro.props;
---
<html lang="en">
<head><title>{title}</title><!-- meta description, canonical, OG/Twitter tags --></head>
<body>
<slot /> <!-- Header, page content, and Footer are all passed in from the page -->
<script src="../scripts/site.js"></script>
<script src="../scripts/animations.js"></script>
</body>
</html>
Page Metadata & the 404 Page
Pass a title and description into Layout on every page for correct browser tabs and search results (see also SEO); an optional ogImage prop overrides the default social preview image. SeoGuru does not currently include a 404.astro file — add one to src/pages/ if you want a custom not-found page instead of your host’s default.
Components
The building blocks pages are assembled from, and how data flows into them.
Reusable Components
Components live in src/components/ and encapsulate one piece of UI so it can be reused across pages without copy-pasting markup — Header.astro, Footer.astro, ServicePage.astro (the shared shell for all three service pages), CaseStudyCard.astro, PricingSection.astro, FaqSection.astro, Testimonial.astro, WhyUs.astro, AuditCta.astro, and ArrowLink.astro. Every component is plain .astro — there’s no .tsx/React in this template.
Props & TypeScript Interfaces
Most components just destructure Astro.props directly with no declared type, which is enough for a plain-JavaScript template like this one. Where a component's prop shape benefits from documenting, an optional interface Props works the same way TypeScript interfaces do in a .tsx file — PricingSection.astro uses one for its three optional props (class, title, description), which lets each page that renders it opt into page-only styling and its own heading without affecting the other:
---
interface Props {
class?: string;
title?: string;
description?: string;
}
const { class: className, title, description } = Astro.props;
---
<section id="pricing" class:list={['pricing-section', className]}>
<div class="w-layout-blockcontainer main-container w-container">
<slot />
<div class="pricing-container">
{title && <h3 class="h2">{title}</h3>}
{description && <p class="body-medium">{description}</p>}
In plain language: all three props are optional (the ?). The homepage renders <PricingSection title="Membership" description="Become a member today and Grow with SeoGuru" />, so it gets a centered heading above the pricing tabs; the pricing page passes neither, since it already has its own hero heading above the section (see below), and instead passes class="pricing-section--flat" for page-only background/spacing.
Component Composition
Larger sections are composed from smaller components rather than written as one long block of markup — ServicePage.astro imports and composes ArrowLink, Testimonial, WhyUs, and AuditCta internally. A component can also accept child markup through a <slot />, the way PricingSection.astro does on the pricing page — the page’s hero heading is passed in as slotted content and rendered inside the pricing section’s own wrapper, rather than living in a second, separate hero section:
<PricingSection class="pricing-section--flat">
<div class="all-case-studies-hero-container pricing-hero-intro">
<div class="tag"><div>Pricing</div></div>
<h1 class="h1">Simple, Transparent SEO Pricing</h1>
</div>
</PricingSection>
Passing Data to Components
Props are passed the same way as HTML attributes and read via Astro.props. The three service pages are the clearest example — each one builds a large data object (hero copy, images, bullet points, package details) and hands the whole thing to ServicePage, which does all the rendering:
---
import ServicePage from '../../components/ServicePage.astro';
const data = {
heroTag: 'SEO - Search Engine Optimization',
heroHeading: 'Grow Online with The ROI Driven SEO Agency',
// ...hero image, widget stats, "why" copy and bullets, package details
};
---
<ServicePage data={data} />
To create a fourth service page, copy one of the existing src/pages/services/*.astro files, edit the values in its data object, and add a link to it in the header dropdown (see Header Navigation).
Styling
Where to change colors, type, and spacing without fighting the build.
Global Styles & CSS Variables
Brand colors are defined once as CSS custom properties in src/styles/seoguru.css and referenced everywhere else. Change a value here and it updates across the whole site:
:root {
--dark: #1d1b2d;
--body: #665f78;
--green-light: #e4fff9;
--green: #009d78;
--orange: #ff4e44;
--purple: #6341f0;
/* plus purple-light, pink-light, yellow-light, white */
}
Four plain stylesheets are imported, in order, in Layout.astro: normalize.css (browser reset), framework.css (Webflow-derived layout/utility classes, kept so the converted markup’s class names still work), seoguru.css (this template’s actual design — colors, typography, section styling), and custom.css (small additions made after the Webflow-to-Astro conversion). When in doubt about which file to edit, start in seoguru.css.
Component Styles
Astro components support scoped <style> blocks — CSS written there only applies to that component, so class names never collide across files. SeoGuru mostly keeps styling centralized in src/styles/ rather than per component, but src/pages/pricing.astro uses a scoped, page-only <style is:global> block to adjust the shared PricingSection component’s spacing (and its cards’ shadow) without touching how it renders on the homepage; the background color still matches the homepage’s --green-light, it’s just repeated here to keep the page’s own padding override self-contained:
<style is:global>
.pricing-section.pricing-section--flat {
background-color: var(--green-light);
padding-top: 270px;
}
</style>
Responsive Design, Typography & Spacing
Breakpoints are handled with plain @media screen and (max-width: …) queries in seoguru.css (the template’s Webflow origins mean the common breakpoints are 991px, 767px, and 479px). Prefer adjusting shared values over overriding sizes on individual elements, so the site keeps a consistent rhythm as you edit it.
The dist/ folder is generated output — any change made there is overwritten on the next build. Always edit files under src/.
Images & Assets
Everything lives in one place: public/, referenced by plain root-relative URLs.
| Location | Use for | Behavior |
|---|---|---|
public/images/ | All page imagery (140 files: a mix of .svg, .webp, and .png) | Copied as-is, no build-time processing |
public/videos/ | The ambient background video used on the About page | Copied as-is |
public/fonts/ | Self-hosted font files | Copied as-is — see Fonts & Icons |
public/favicon.ico, public/webclip.png | Browser tab icon and Apple touch icon, linked from Layout.astro | Copied as-is |
SeoGuru does not use src/assets/ or Astro’s built-in <Image /> component (from astro:assets) anywhere — every image is referenced directly with a plain <img> tag and a /images/… path.
Replacing Images
Swap the file in public/images/ in place (keeping the same filename), or replace the src path in the component/page markup to point at a new file:
<img
src={data.heroImage}
loading="lazy"
sizes="(max-width: 479px) 100vw, (max-width: 767px) 88vw, (max-width: 991px) 77vw, 32vw"
srcset={data.heroImageSrcset}
alt=""
/>
Responsive images (like the service page heroes above) supply their own hand-written srcset/sizes attributes pointing at pre-exported image sizes — there is no automatic responsive-image generation.
Supported Formats & SVGs
The template ships a mix of SVG (icons and some illustrations, several inlined directly in component markup so they can inherit color via currentColor — see the social icons in Header.astro and Footer.astro), WebP, and PNG. There is no GIF or AVIF usage.
Image Optimization
Because images are served from public/ rather than imported through astro:assets, nothing is resized, re-encoded, or compressed at build time. Images should be optimized and exported at their intended display sizes before you add them to public/images/.
Everything — hero photos, icons, and files that need a fixed URL — goes in public/ for this template. If you later want Astro to resize and hash images automatically, you’d need to move them into src/assets/ and switch the relevant <img> tags to Astro’s <Image /> component.
Fonts & Icons
Swapping typefaces and icon sets without touching every component.
Google Fonts
SeoGuru does not load any fonts from Google Fonts — every typeface is self-hosted (see Local Fonts below). If you add a Google Font, the <link> tags would go in src/layouts/Layout.astro’s <head>.
Local Fonts
Three self-hosted typefaces ship in public/fonts/ and are declared with @font-face at the top of src/styles/seoguru.css:
| Family | Weights | Files |
|---|---|---|
Onest | 100, 300, 400, 500, 700, 900 | Onest-Thin.ttf through Onest-Black.ttf |
Satoshi Variable | 300–900 (variable) | Satoshi-Variable.ttf |
Generalsans Variable | 200–700 (variable) | GeneralSans-Variable.ttf |
@font-face {
font-family: Onest;
src: url('/fonts/Onest-Regular.ttf') format("truetype");
font-weight: 400;
font-style: normal;
font-display: swap;
}
To swap a typeface, replace the relevant .ttf files in public/fonts/, update the matching @font-face blocks, and update the font-family references used elsewhere in seoguru.css. Check each font’s license (see CREDITS.md) before redistributing it.
Icon Libraries
There is no icon library or sprite sheet. Icons are inline <svg> markup written directly in component files (for example, the social icons in Header.astro and Footer.astro, using fill="currentColor" so they inherit text color) or standalone files in public/images/ referenced with a plain <img> tag.
Replacing & Adding Icons
For an inline SVG, replace the <svg>…</svg> markup directly in the component. For an <img>-based icon, drop a new SVG or PNG into public/images/ and update the src path.
Content Editing
The fastest, lowest-risk way to make the site say what you want it to say.
Everything in this section is plain text or attribute changes — no logic to break. Save the file, and your dev server (from Getting Started) will refresh the browser automatically so you can see the change right away.
Headings, Paragraphs & Buttons
Find the relevant page in src/pages/ (or the component it imports), locate the text between the tags, and edit it directly. Service pages keep almost all of their copy in one data object at the top of the file (see Passing Data to Components) rather than scattered through the markup:
// before
heroHeading: 'Grow Online with The ROI Driven SEO Agency',
// after
heroHeading: 'Rank Higher with Our Award-Winning SEO Team',
Navigation & Footer
Header links live directly in src/components/Header.astro’s markup, and footer link groups live directly in src/components/Footer.astro’s markup — there is no separate navigation data file for this template. See Navigation for the full pattern.
Cards, Testimonials & Pricing
The FAQ questions shown on both the homepage and the pricing page are the one piece of repeating content driven by a shared data array — add, remove, or reword an entry and it updates everywhere FaqSection is used:
export const faqs = [
{
q: 'What factors determine the pricing of SEO services?',
a: "The pricing of SEO services can be influenced by …",
},
// ...
];
Pricing tiers (Basic/Advanced/Custom package cards) and testimonial quotes are hand-written directly inside src/components/PricingSection.astro and src/components/Testimonial.astro respectively, rather than generated from a data array — edit those component files to change their content.
Blog Content & Contact Information
Case studies are Markdown files in src/content/case-studies/ — each file’s front matter must match the schema in content.config.js (title, images, category, tags, a client object, objectives, approach steps, and so on). Add a new file there to add a new case study; it automatically appears in the list, its category archive, and its tag archive. Contact details are currently placeholder values repeated in a few places rather than centralized in one file: the email and phone in src/pages/contact.astro, and the phone number in src/components/Header.astro. Replace all of them with your real details before launch (see Production Checklist).
Forms
What ships out of the box, and what still needs a backend.
SeoGuru’s audit request, contact, and newsletter forms are intentionally shipped as UI-only — no action or method attribute is set on any of them. They will not deliver anywhere until you wire each one to a form handler of your choice.
Contact Forms
Forms are plain HTML — no JavaScript form component or client-side framework is involved. All three files just have a bare <form> tag; add your own action/method to each:
<form class="contact-form">
The other two are the audit-request form in src/components/AuditCta.astro and the newsletter signup form in src/components/Footer.astro.
Newsletter Forms
The footer newsletter form is not pre-wired to a mailing-list provider (Mailchimp, ConvertKit, etc.) — like the other two forms, it has no action set and only collects an email address.
Form Validation
Required fields and email format are checked in the browser using standard HTML attributes (required, type="email", maxlength) — there is no custom JavaScript validation logic.
Third-Party Form Services
None of the forms are wired to a provider out of the box. Pick a service that accepts submissions directly from static HTML (Formspree, Getform, Basin, Netlify Forms, etc.), create a form there, and add the resulting action (and method="POST") to each of the three files above — or point them at your own API route.
API & Data
SeoGuru does not fetch data from any external API.
Every page renders from data that already lives in the repository at build time: hard-coded markup and data objects in the page/component files, the faqs.js array in src/data/, and the case-studies content collection in src/content/. There is no fetch() call, API client, or environment variable anywhere in src/.
API Configuration
Not applicable — there is nothing to configure. If you add an external API integration later, see Environment Variables (.env) for the standard Astro convention for storing the URL/key.
Fetch Requests
The closest thing to a data query in this template is Astro’s built-in content collection API, used to read the case studies at build time:
import { getCollection } from 'astro:content';
const posts = await getCollection('case-studies');
Data Structures
The one documented data shape in this template is the case-studies collection schema, defined with Zod in content.config.js: title, image, heroImage, category, tags (array), date, order, author, excerpt, a client object (business, industry, location, challenge), objectives (array), approach (array of { label, description }), innerImage, result, quote, and an optional quoteAuthor.
Error Handling
Not applicable — there are no runtime network requests to fail. A case-study Markdown file that doesn’t match the schema above will fail the build with a validation error, rather than fail silently at runtime.
SEO
Per-page metadata, social previews, and the machine-readable files search engines expect.
Page Title & Meta Description
Pass a unique title and description into Layout on every page — these drive both the browser tab and the snippet shown in search results. Layout.astro also appends | SeoGuru to whatever title you pass:
<Layout
title="Pricing"
description="Simple, transparent SEO pricing plans built to grow with your business."
>
<!-- renders as <title>Pricing | SeoGuru</title> -->
Canonical URL, Open Graph & Twitter/X Cards
Set inside Layout.astro’s <head>, using Astro.site (the site value from astro.config.mjs) to build absolute URLs. An optional ogImage prop overrides the site-wide default:
---
const { title, description, ogImage = '/images/Hero-Image-2.webp' } = Astro.props;
const siteUrl = Astro.site ? new URL(Astro.url.pathname, Astro.site).toString() : Astro.url.pathname;
---
<link rel="canonical" href={siteUrl} />
<meta property="og:title" content={pageTitle} />
<meta property="og:image" content={new URL(ogImage, Astro.site ?? Astro.url.origin)} />
<meta name="twitter:card" content="summary_large_image" />
Sitemap & Robots.txt
@astrojs/sitemap is registered in astro.config.mjs and generates sitemap-index.xml automatically at build time once the site value is set to your real domain. SeoGuru does not currently include a public/robots.txt file — add one if you want to control crawler access or explicitly point crawlers at the generated sitemap:
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap-index.xml
Structured Data & Semantic HTML
Pages use landmark elements (<section>, <nav>) and heading hierarchy, but SeoGuru does not currently include any JSON-LD structured data. If rich search results matter to you, add Organization or Article schema to Layout.astro’s <head> (or per-page, for case studies).
Deployment
Platform-by-platform steps. Confirm your rendering mode first — it changes the build output you deploy.
SeoGuru uses Astro’s default static output — no SSR adapter is configured. npm run build produces plain HTML/CSS/JS in dist/ that any static host can serve; you don’t need a Node-capable server at runtime.
Vercel
- Push the project to a GitHub, GitLab, or Bitbucket repository.
- In Vercel, choose Add New → Project and import that repository.
- Build command:
npm run build· Output directory:dist— Vercel’s Astro preset detects this automatically in most cases. - No environment variables are required for this template (see Environment Variables).
- Click Deploy.
Netlify
[build]
command = "npm run build"
publish = "dist"
Connect the repository in the Netlify dashboard, confirm these build settings, and deploy. No environment variables are required.
Cloudflare Pages
Connect the repository, set the build command to npm run build and the output directory to dist.
GitHub Pages
Set site (and base, if deploying to a project subpath rather than a custom domain) in astro.config.mjs to match your repository URL, then build and publish the dist/ folder to the gh-pages branch (via the official astro build + a deploy action, or manually).
Traditional Hosting
Upload the contents of dist/ to your host’s public web root via FTP/SFTP or your host’s file manager — since this is a static build, any plain web host works, with no Node.js runtime required in production.
Build & Production
What happens when you run build, and a checklist before you ship.
npm run build # type-check, bundle, and write dist/
npm run preview # serve dist/ locally, exactly as it will run in production
Build Output
The build writes static HTML and the CSS/JS/image assets referenced from public/ to dist/. This folder is disposable and regenerated by every build — it’s conventional to keep it out of Git via .gitignore.
Environment Variables in Production
Not applicable — this template has no environment variables to set in any environment (see Configuration).
Production Checklist
- Run
npm run buildlocally and confirm it completes with no errors. - Run
npm run previewand click through the main pages. - Set
siteinastro.config.mjsto your real deployed URL (currently the placeholderhttps://example.com) — it feeds the sitemap and canonical/OG tags. - Wire the forms in
src/components/AuditCta.astro,src/components/Footer.astro, andsrc/pages/contact.astroto a form handler of your choice — they currently have noactionset. - Swap the placeholder contact details —
hello@example.com,+1 (555) 010-0123, the Springfield address incontact.astro, and the1234-567-890phone number inHeader.astro— for your real ones. - Replace the placeholder social links (bare
facebook.com/twitter.com/skype.com) inHeader.astroandFooter.astrowith your real profile URLs. - Replace the sample case studies in
src/content/case-studies/with your own. - Verify the favicon (
public/favicon.ico), Open Graph image, and per-page meta descriptions are your own, not the template defaults.
Troubleshooting
The most common problems, in the order you’re likely to hit them.
npm install fails
- Cause
- Node.js version too old, a corrupted local cache, or a stale lockfile from a different package manager.
- Solution
- Confirm
node --versionmeets the requirement in Requirements, then runrm -rf node_modules package-lock.json && npm install.
Port already in use
- Cause
- Another process (often a previous dev server that didn’t shut down cleanly) is already listening on the default port.
- Solution
- Stop the other process, or start this one on a different port:
npm run dev -- --port 4322.
Module not found
- Cause
- A missing dependency, or a typo in a relative import path (this template has no path aliases — every import uses a relative path like
../../components/Header.astro).
- Solution
- Re-run
npm install; check the import path and file casing match exactly.
Images not loading
- Cause
- An incorrect path. Every image in this template lives in
public/, so it needs a root-relative URL, not a relative one.
- Solution
- Review Images & Assets — reference files under
public/images/as/images/filename.webp, not./images/filename.webpor../public/images/filename.webp.
Styles not loading
- Cause
- The stylesheet isn’t imported into
Layout.astro, or the CSS load order changed —normalize.css,framework.css,seoguru.css, andcustom.cssare imported in that specific order, and a later file can intentionally override an earlier one.
- Solution
- Confirm all four stylesheets are still imported at the top of
src/layouts/Layout.astro, in their original order.
Editor shows a TypeScript error in a .astro file
- Cause
- This template isn’t written in TypeScript, but
tsconfig.jsonstill extends Astro’s strict preset, so your editor type-checks frontmatter. Most components have no declaredinterface, so an error is usually a genuine typo rather than a props mismatch — except inPricingSection.astro, the one component with a declaredPropsinterface (see Props).
- Solution
- Read the error’s file and line number. It will not block
npm run dev, but it can failnpm run build— fix it before shipping.
Build fails (but dev server works)
- Cause
- The dev server is more forgiving than
build, which fully type-checks the project — an issue tolerated in dev can fail the build.
- Solution
- Read the full terminal output for the first error (later errors are often side effects of it); fix issues top to bottom and re-run.
Environment variables not working
- Cause
- This template ships with no
.envfile or environment variables at all (see Configuration), so this only applies if you’ve added your own. The usual cause is a missingPUBLIC_prefix for a client-side value, or the dev server not being restarted after editing.env.
- Solution
- Rename the variable with the
PUBLIC_prefix if it’s used in browser code, then stop and restartnpm run dev— env changes aren’t hot-reloaded.
Deployment problems
- Cause
- Wrong output directory configured on the host (it should be
dist), or thesitevalue inastro.config.mjsstill pointing at the placeholderhttps://example.com.
- Solution
- Re-check the build command and output folder against Deployment for your platform, and confirm
sitematches your real domain (see Production Checklist).
FAQ
Quick answers to the questions we hear most.
How do I install the template?
Extract the ZIP, run your package manager’s install command, then start the dev server. The full walkthrough is in Getting Started.
Which Node.js version should I use?
Node 22.12.0 or later — see Requirements. This is set in package.json’s engines field.
Does this template use TypeScript or React?
No. It’s plain Astro and vanilla JavaScript/CSS — there are no .tsx files and only one component declares an optional interface Props (see Props). The tsconfig.json that ships with it only enables editor type-checking on .astro frontmatter; there’s no TypeScript compilation step to opt out of.
How do I change colors?
Update the CSS custom properties in src/styles/seoguru.css’s :root block — see Styling.
How do I change fonts?
Swap the font import and update the corresponding --font-* variable — see Fonts & Icons.
How do I replace images?
Swap the file or update the import path — see Images & Assets.
How do I add a new page?
Add a file to src/pages/; the route is created automatically — see Pages.
How do I deploy the template?
Platform-specific steps for Vercel, Netlify, Cloudflare Pages, GitHub Pages, and traditional hosts are in Deployment.
Can I use the template commercially?
[State your license terms here — e.g. standard vs. extended license — and link to the full license text.]
Where can I get support?
See the Support section for contact details and what to include in a request.
Changelog
Every notable change, newest first. Add a new entry above the last one for each release.
- AddedInitial release: homepage, about, contact, three service pages, a pricing page, and a case-study blog with category/tag archives, converted to Astro from the original Webflow export.
Copy the markup for one entry above, update the version number, date, and bullet list, and place it at the top of this section for each new release.
Support
Stuck on something this guide didn’t cover? Here’s how to reach us.
Documentation
Most questions are answered somewhere in this guide — try the search (Ctrl K) before reaching out.
Back to topSupport hours
We’re based in India and available Monday–Friday, 10am–7pm IST. Expect a response within 24 hours.
What to Include in a Support Request
The fastest way to get a useful answer is to include the details below in your first message:
When contacting support, please include:
- Template name and version
- Browser (and version)
- Operating system
- Node.js version (node --version)
- The exact error message
- Steps to reproduce the problem