Revolution 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 Revolution, a creative-agency website template for Astro. 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 project source in the revolution-astro/ folder — components, pages, data files, blog posts, scripts, styles, and configuration.
Production build
Not pre-built — no dist/ folder ships in the package. Run npm run build to generate the static site (see Build & Preview).
Design assets
Demo images (logos, photos, client logos) in public/images/, the Satoshi font files in public/fonts/, and an inline SVG icon set in src/data/icons.js. No Figma or other design source files are included.
This documentation
Hosted online — the link is in documentation.txt, next to the template folder in the download.
Features
- Agency home page with a large hero, services slider, work marquee and grid, awards band, and testimonials
- About, Service, and Contact pages, plus a standalone 404 page
- Seven portfolio grid layouts (2, 3, 4, 5, and 6 columns, 3-column masonry, 4-column metro) with category filtering, category pages, and 12 single-project page variants
- Markdown blog with five post formats (standard, gallery, video, audio, quote), pagination, and category, tag, author, and format archives
- GSAP entrance and scroll animations that respect
prefers-reduced-motion, plus Swiper sliders, a lightbox, masonry, parallax, and magnetic buttons - All page copy kept in plain JavaScript data files under
src/data/ - Canonical, Open Graph, and Twitter card tags on every page, and an automatic
sitemap-index.xml - Self-hosted fonts — no requests to Google Fonts
Technology Stack
Revolution is built with plain .astro components, vanilla JavaScript, and plain CSS — no UI framework, no TypeScript, and no CSS preprocessor or utility framework.
Requirements
| Tool | Minimum version | Notes |
|---|---|---|
| Node.js | 22.12.0 or later | Set in package.json under engines; Astro 7 requires it |
| Package manager | npm (bundled with Node.js) | A package-lock.json is included, so npm is the default; pnpm or Yarn also work |
| Code editor | — | VS Code with the Astro extension (recommended in .vscode/extensions.json) 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. Revolution ships no polyfills.
Getting Started
From a downloaded ZIP to a running dev server in five steps.
Installation & Extraction
- Download
revolution-astro-package.zipfrom your purchase confirmation email or account downloads page. - Extract it to a folder on your machine — avoid folder names with spaces or special characters.
- Inside you’ll find the
revolution-astro/template folder anddocumentation.txt. Openrevolution-astro/in your terminal and in your code editor.
# Extract on macOS / Linux
unzip revolution-astro-package.zip
cd revolution-astro-package/revolution-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 Vite’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.
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 validates the blog content against its schema, renders every page to static HTML, bundles and minifies CSS/JS, and writes the 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 invalid blog front matter or a missing import on a page you didn’t open) 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/ | Files here are copied to the output root untouched — no optimization or hashing. Revolution keeps all its images here (public/images/) along with the Satoshi fonts and favicons. |
src/components/ | Header, Footer, and Icon at the top level; page sections in sections/<page>/ (home, about, service, contact, portfolio, blog, post, archive, error) and sections/shared/; small building blocks in ui/. |
src/layouts/ | Wrapper templates that define the common page shell — <head> tags, header, footer — so individual pages only supply their unique content. |
src/pages/ | Every file here becomes a route automatically. See Pages for details. |
src/content/blog/ | One Markdown file per blog post; the front matter schema lives in src/content.config.js. See Content Editing. |
src/scripts/ | Small vanilla JavaScript modules for client-side behavior: reveal.js, magnetic.js, parallax.js, lightbox.js, masonry.js, cursor-link.js, share-links.js. |
src/styles/ | A single global.css with the font declarations, CSS variables, and base styles. See Styling. |
src/data/ | Page copy and settings as plain JavaScript exports — site.js, navigation.js, footer.js, home.js, about.js, service.js, contact.js, portfolio*.js, blog.js, and more — kept separate from markup so it’s easy to edit without touching Astro templates. |
.vscode/ (recommends the Astro extension) and .gitignore also sit at the project root. node_modules/, .astro/, and dist/ are created by npm install, npm run dev, and npm run build — don’t edit them by hand.
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": "revolution-astro",
"type": "module",
"version": "0.0.1",
"engines": { "node": ">=22.12.0" },
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/sitemap": "^3.7.4",
"@fontsource-variable/outfit": "^5.3.0",
"@fontsource/manrope": "^5.3.0",
"@fontsource/playfair-display": "^5.3.0",
"astro": "^7.3.8",
"gsap": "^3.15.0",
"swiper": "^14.3.0"
}
}
astro.config.mjs
Controls integrations, the deployed site URL, and build behavior. Revolution uses one integration, @astrojs/sitemap, and renders every page statically. Replace site with your real domain before deploying.
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
// Replace with the deployed URL: used for canonical links, Open Graph images and the sitemap.
site: 'https://example.com',
// The dev toolbar overlays the page and would show up in screenshot comparisons.
devToolbar: { enabled: false },
integrations: [sitemap()],
});
tsconfig.json
Revolution contains no TypeScript code — this is Astro’s default file, kept so your editor understands .astro files and Astro’s generated types. No path aliases are defined, so imports use relative paths (e.g. ../components/Header.astro).
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}
Environment Variables (.env)
Revolution doesn’t read any environment variables out of the box, so no .env file is needed to run it. If you add an integration that needs one, create a .env file in the project root for values that differ between environments (API URLs, keys) or shouldn’t be committed to Git. Variables must be prefixed PUBLIC_ to be readable in client-side code.
# 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
Add .env to .gitignore (it usually is by default). Anything without the PUBLIC_ prefix should stay off the client entirely.
Pages
How routes are created, and which file builds which URL.
Creating a New Page
Astro uses file-based routing: any .astro or .md file placed in src/pages/ automatically becomes a route matching its file path. Revolution’s routes:
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/service.astro → /service
src/pages/contact.astro → /contact
src/pages/portfolio-3-column.astro → /portfolio-3-column (+ 2, 4, 5, 6-column, masonry, metro)
src/pages/portfolio/[slug].astro → /portfolio/:slug (12 project pages)
src/pages/portfolio-category/[slug].astro → /portfolio-category/:slug
src/pages/blog/[...page].astro → /blog, /blog/page/2 …
src/pages/[slug].astro → /:post-slug (single blog posts)
src/pages/category/[slug]/[...page].astro → /category/:slug
src/pages/tag/[slug]/[...page].astro → /tag/:slug
src/pages/author/[slug]/[...page].astro → /author/:slug
src/pages/type/[slug]/[...page].astro → /type/:format (gallery, video, audio, quote)
src/pages/404.astro → any unmatched URL
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 Testimonials from '../components/sections/shared/Testimonials.astro';
import { testimonials } from '../data/testimonials.js';
---
<Layout title="Services – Revolution – Agency" description="What we do.">
<Testimonials {...testimonials} />
</Layout>
Editing an Existing Page
Revolution’s pages are thin: each file under src/pages/ imports its sections and passes them data from src/data/. To change what a page says, edit its data file (e.g. src/data/about.js); to change how a section looks or is structured, edit the component in src/components/sections/; to add, remove, or reorder sections, edit the page file itself.
Dynamic Routes
Files wrapped in square brackets, such as [slug].astro, generate multiple pages from a single template — commonly used for blog posts or product pages. Astro requires a getStaticPaths() function for these on statically-generated sites. Revolution’s project pages are built from the list in src/data/portfolio-projects.js:
---
import { projectPages } from '../../data/portfolio-projects.js';
export function getStaticPaths() {
return projectPages.map((page) => ({ params: { slug: page.slug }, props: { page } }));
}
---
Layouts
Layouts (in src/layouts/) wrap page content with shared structure — the <html> shell, <head> metadata, header, and footer. Revolution has three: Layout.astro (used by every page), PostLayout.astro (single blog posts), and ArchiveLayout.astro (category, tag, author, and format archives), and the last two render inside Layout. Page content is injected via a <slot />:
---
const {
title = site.name,
description = '',
image, // Open Graph image
headerScheme = 'dark', // 'dark' or 'light' header logo/text
footerMarquee = true, // show the "Talk to us" marquee in the footer
background, // page background color
chrome = true, // false hides header and footer (404)
noindex = false,
} = Astro.props;
---
<html lang="en">
<head><title>{title}</title></head>
<body>
{chrome && <Header scheme={headerScheme} />}
<main class="site-main"><slot /></main> <!-- page content is injected here -->
{chrome && <Footer marquee={footerMarquee} />}
</body>
</html>
Page Metadata & the 404 Page
Pass a title and description into your layout’s props on every page for correct browser tabs and search results (see also SEO). A file named 404.astro in src/pages/ is served automatically whenever no route matches; Revolution’s renders without header and footer (chrome={false}), is marked noindex, and takes its copy from src/data/not-found.js.
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 — a button, a pricing card, a navigation bar — so it can be reused across pages without copy-pasting markup. Every component in Revolution is a plain .astro file, organized as:
Header.astro,Footer.astro— site chrome, including the menusIcon.astro— renders an SVG fromsrc/data/icons.jsby namesections/<page>/— sections used by one page type (home/Hero.astro,about/AboutTeam.astro,contact/ContactMain.astro,portfolio/CaseHero.astro…)sections/shared/— sections reused across pages (Testimonials,PortfolioGrid,PostCard,FeaturedVideo…)ui/— small building blocks:CtaCircle,LineLink,Marquee
Props
Props are the inputs a component accepts, similar to HTML attributes. Revolution uses plain JavaScript (no TypeScript interfaces), so each component documents its props by destructuring them from Astro.props at the top of the file, with defaults where a prop is optional:
---
// Round call-to-action: a colour fill rises from the top on hover,
// and the button drifts toward the cursor.
const {
href,
label,
size = 136,
background = 'var(--color-primary)',
fill = 'var(--color-accent)',
color = 'var(--color-white)',
hoverColor = color,
fontSize = 15,
letterSpacing = '0',
class: className,
} = Astro.props;
---
In plain language: href and label are expected on every use; everything else falls back to its default, so <CtaCircle href="/contact/" label="Let’s Talk" /> renders a 136 px black circle with a green hover fill. Nothing checks prop types at build time, so a misspelled prop name is simply ignored — compare against the destructuring block if a prop seems to have no effect.
Component Composition
Larger sections are usually composed from smaller components rather than written as one long block of markup:
<Layout title={page.title} description={page.description}>
<Hero {...hero} />
<ServicesSlider {...services} />
<GlobalPresence {...presence} />
<FeaturedVideo {...featuredVideo} />
<WorkMarquee {...workMarquee} />
<WorkGrid {...workGrid} />
<AwardsBand {...awards} />
<Testimonials {...testimonials} />
</Layout>
Sections, in turn, compose the ui/ pieces — Hero uses CtaCircle, and many sections use Icon and LineLink.
Passing Data to Components
In Astro, props are passed the same way as HTML attributes and read via Astro.props:
---
import LineLink from '../components/ui/LineLink.astro';
---
<LineLink href="/service/" label="Discover" />
Page sections usually receive a whole object from a data file via the spread syntax (<Hero {...hero} />), so each key of the hero export in src/data/home.js becomes a prop.
Components render to static HTML. Interactive behavior — the menus, sliders, portfolio filtering, form validation, animations — comes from a <script> block at the bottom of the relevant .astro file (importing GSAP or Swiper as needed) or from a shared module in src/scripts/. Astro bundles these automatically; no client: directives or UI framework are involved.
Styling
Where to change colors, type, and spacing without fighting the build.
Global Styles & CSS Variables
Site-wide values — color palette, font families, type scale, container widths — are defined once as CSS custom properties at the top of src/styles/global.css (imported by Layout.astro) and referenced everywhere else. Change a value here and it updates across the whole site.
:root {
/* Colors */
--color-primary: #000000;
--color-secondary: #202020;
--color-text: #48495d;
--color-accent: #00a57d;
--color-yellow: #ffca50;
--color-mint: #eefff8;
--color-sky: #ecf6ff;
--color-blush: #fff3f8;
--color-peach: #fff4f3;
--color-lavender: #f0e8fd;
--color-white: #ffffff;
/* Fonts */
--font-heading: 'Outfit Variable', 'Outfit', sans-serif;
--font-body: 'Satoshi', sans-serif;
/* Layout */
--container: 1200px;
--container-wide: 1220px;
--header-height: 111px;
}
Component Styles
Astro components support scoped <style> blocks — CSS written there only applies to that component, so class names never collide across files.
<style>
.line-link { color: var(--color-primary); }
.line-link__text { font-family: var(--font-heading); }
</style>
Every Revolution component keeps its own styles in such a block, so to restyle a section, open its .astro file rather than searching global.css.
Responsive Design, Typography & Spacing
The type scale (--text-display, --text-marquee, and their line heights) and container widths are tokens in global.css, and are re-declared at each breakpoint there. The breakpoints used throughout the components are: mobile ≤767px, mobile landscape ≤880px, tablet ≤1024px, tablet landscape ≤1200px, and laptop ≤1366px. Prefer adjusting these 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
Where Revolution keeps its images, and how to swap them.
| Location | Use for | Behavior |
|---|---|---|
public/images/ | Every image in the template, in folders per page: home/, about/, service/, portfolio/, blog/, shared/ (logos, client logos, menu image) | Copied as-is, no processing; referenced by URL, e.g. /images/home/hero-bg.jpg |
public/ root | favicon.svg and favicon.ico | Copied as-is, no processing |
src/assets/ | Not used by Revolution — create it if you want Astro’s optimized <Image> pipeline for new images | Optimized, resized, and content-hashed by the build |
Replacing Images
Swap the file in public/images/ in place (keeping the same filename), or drop in a new file and update the path where it’s referenced — almost always in a src/data/*.js file or in a blog post’s front matter. Keep the width/height values in step with the new image so the layout doesn’t shift while it loads:
export const hero = {
background: '/images/home/hero-bg.jpg',
lineOne: { word: 'Creative', image: { src: '/images/home/hero-portrait-right.png', width: 147, height: 176 } },
// …
};
The logo is set in src/data/site.js (logo.dark and logo.light, both SVGs in public/images/shared/).
Supported Formats & SVGs
Because images are served from public/ through plain <img> tags and CSS backgrounds, any format the browser supports works — the demo uses JPEG, PNG, and SVG. The logos are SVG files referenced as <img> sources; UI icons are inline SVG paths rendered by Icon.astro so they inherit color via currentColor (see Icons).
Image Optimization
Files in public/ are not optimized by the build, so resize and compress images before adding them. The demo ships pre-cut sizes for listings — e.g. a blog post has a full image, a 1200×790 thumbnail, and a 650×544 card crop — and it’s worth keeping that pattern for your own images. Images below the fold use loading="lazy".
To stay consistent with the rest of the template, put new images in the matching public/images/<page>/ folder and reference them by root-relative URL. Only move to src/assets/ + astro:assets if you want build-time optimization, which also means changing the component that renders the image.
Fonts & Icons
Swapping typefaces and icon sets without touching every component.
Google Fonts (via Fontsource)
Revolution doesn’t link to the Google Fonts CDN. Its Google-hosted typefaces are installed as npm packages from Fontsource and bundled with the site: Outfit (headings, imported in global.css), Manrope (blog post body, imported in PostLayout.astro), and Playfair Display (the quote in QuoteSplit.astro). To swap one, install the new Fontsource package, change the import, then update the matching --font-* variable:
@import '@fontsource-variable/outfit';
:root {
--font-heading: 'Outfit Variable', 'Outfit', sans-serif;
}
Local Fonts
The body font, Satoshi (weights 500 and 700), is self-hosted in public/fonts/ and declared with @font-face at the top of global.css. Layout.astro also preloads satoshi-500.woff2, so update that <link rel="preload"> if you replace it:
@font-face {
font-family: 'Satoshi';
src: url('/fonts/satoshi-500.woff2') format('woff2'), url('/fonts/satoshi-500.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
Icon Libraries
Revolution uses no external icon library. Its icons (arrows, close, social logos, and so on) are stored as SVG path data in src/data/icons.js and rendered by src/components/Icon.astro, which takes a name, an optional size, and an optional class, and fills with currentColor:
<Icon name="arrow-up-right" size={13} />
Replacing & Adding Icons
To change an icon’s color, set color on its parent in CSS. To add a new icon, add an entry to src/data/icons.js with the SVG’s viewBox and the d attribute of its single <path>, then use it by name. Icons made of several paths need to be merged into one path first (most SVG editors can do this). Social icons are picked by name in src/data/navigation.js and src/data/footer.js.
export const icons = {
"arrow-right": {
"viewBox": "0 0 17 17",
"d": "M12.8112 7.37197L7.44725 2.00796…"
},
// …
};
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
In Revolution, page text lives in the data file named after the page — src/data/home.js, about.js, service.js, contact.js, not-found.js, and portfolio-pages.js / portfolio-projects.js for the portfolio. Each section has its own export; edit the text values between the quotes:
// before
export const hero = {
intro: 'Hello, we’re Revolution, an award winning..',
lineOne: { word: 'Creative', /* … */ },
lineTwo: { word: 'Agency', /* … */ },
cta: { label: 'Work', href: '/portfolio-3-column/' },
};
// after
export const hero = {
intro: 'Hello, we’re Riverside, an independent..',
lineOne: { word: 'Design', /* … */ },
lineTwo: { word: 'Studio', /* … */ },
cta: { label: 'Projects', href: '/portfolio-3-column/' },
};
Each page’s browser-tab title and meta description sit in the same file, in its page export (e.g. page.title in home.js).
Navigation & Footer
Menu links, the header “Let’s Talk” button, the featured project in the menu, and social links live in src/data/navigation.js; the footer marquee, menu, social icons, and copyright line (© 2026 RNN Studio) live in src/data/footer.js. Edit the label and URL once and every page updates. See Navigation for the full pattern.
Cards, Testimonials & Pricing
Repeating content blocks — service cards, team members, portfolio projects, testimonials, client logos — are driven by data arrays rather than hand-written per card, so adding or removing an item is a matter of editing the list. The testimonials band (shown on the home and service pages) is a good example:
export const testimonials = {
eyebrow: '96.3 Customer Satisfaction',
titleLines: ['Hear from our', 'happy clients'],
testimonials: [
{ quote, avatar, name: 'Linda Miller', role: 'COO of Paytime' },
{ quote, avatar, name: 'Linda Miller', role: 'COO of Paytime' },
],
logos: [logo(0, 100), logo(1, 147), logo(2, 100), logo(3, 119), logo(4, 112)],
};
Portfolio projects work the same way: the cards in every grid come from projects in src/data/portfolio.js, and each project page’s sections are listed in src/data/portfolio-projects.js.
Blog Content & Contact Information
Blog posts are Markdown files in src/content/blog/ — one file per post. Edit the front matter and body directly, or copy an existing post to add a new one. Fields are checked against the schema in src/content.config.js; format switches between the standard, gallery, video, audio, and quote layouts:
---
title: "A closer look at connected living"
slug: "a-closer-look-at-connected-living"
date: "2022-05-20T12:24:49"
excerpt: "Our most successful engagements have been…"
author: "golothemes"
categories: ["Article", "Digital", "Technology"]
tags: ["Art", "Mobile Apps", "Photography", "Web"]
image: "/images/blog/girl-with-phone-1.jpg"
thumbnail: "/images/blog/girl-with-phone-1-1200x790.jpg"
card: "/images/blog/girl-with-phone-1-650x544.jpg"
---
Which posts appear in the slider at the top of /blog, and how many posts show per page, is set in blogPage in src/data/blog.js; author bios are in the same file. The demo comment thread under each post comes from src/data/comments.js. The site name, logo, phone number, and email address are set once in src/data/site.js and reused by the menu, footer, and contact page.
Forms
What ships out of the box, and what still needs a backend.
Revolution has two forms — the contact form on /contact and the “Leave a Reply” comment form under each blog post. Both render the full UI, but neither is connected to a working service: their action is the placeholder https://formspree.io/f/your-form-id. Until you replace it, submissions fail.
Contact Forms
The contact form’s fields, button text, messages, and endpoint are all set in src/data/contact.js. Replace action with your endpoint:
form: {
// Wire this up to your form service (Formspree, Web3Forms, Netlify Forms…)
action: 'https://formspree.io/f/your-form-id',
method: 'POST',
fields: [
{ name: 'name', type: 'text', placeholder: 'Name', required: true },
{ name: 'email', type: 'email', placeholder: 'Email', required: true },
{ name: 'message', type: 'textarea', placeholder: 'Message', required: true },
],
submit: 'Submit',
},
The script in src/components/sections/contact/ContactMain.astro submits the form with fetch (sending FormData with an Accept: application/json header), then shows the “sent” or “failed” message from contact.js without leaving the page. The endpoint must therefore accept a cross-origin POST and return a 2xx status on success. The comment form’s endpoint is commentForm.action in src/data/comments.js; it is a plain HTML form post with no script.
Newsletter Forms
Revolution doesn’t include a newsletter signup form, and no email provider is pre-wired. To add one, use your provider’s embed form in a new section, following the same pattern as the contact form.
Form Validation
The contact form uses novalidate to switch off the browser’s own pop-ups and validates with a small script instead: empty required fields and invalid type="email" values get an inline message under the field (with aria-invalid and aria-describedby set), and nothing is sent until every field passes. The message texts are in messages in src/data/contact.js.
Third-Party Form Services
If you’d rather not run your own backend, services like Formspree, Getform, or Basin accept form submissions directly from static HTML and forward them to your email — typically a one-line change to the form’s action attribute.
API & Data
Only relevant if your template fetches data from an external source — skip this section otherwise.
Every page is built from local files: the JavaScript exports in src/data/ and the Markdown blog collection in src/content/blog/. There are no API calls, API keys, or environment variables. The only network request the site makes is the form submission described in Forms. The subsections below are general guidance in case you connect a CMS or API later.
API Configuration
API URLs and keys are read from environment variables (see Configuration) rather than hard-coded, so the same code can point at a staging or production API depending on environment:
PUBLIC_API_URL=https://example.com/api
PUBLIC_API_KEY=your_api_key
The values above are placeholders. Keep real API keys out of version control, and prefer server-only variables (without the PUBLIC_ prefix) for anything sensitive.
Fetch Requests
const res = await fetch(`${import.meta.env.PUBLIC_API_URL}/products`, {
headers: { Authorization: `Bearer ${import.meta.env.PUBLIC_API_KEY}` },
});
const data = await res.json();
Data Structures
The one structured data source with a formal schema is the blog collection, defined in src/content.config.js. If you move posts to a CMS, map its fields to this shape so the existing components keep working:
| Field | Type | Notes |
|---|---|---|
title, slug | string | Required; slug becomes the post URL (/<slug>) |
date | date | Required; read as UTC when no time zone is given. updated is optional |
excerpt, author | string | Optional |
categories, tags | string[] | Default to empty; drive the category and tag archives |
image, thumbnail, card | string | Optional image URLs: full image, 1200×790 listing crop, 650×544 related-posts crop |
format | enum | standard (default), gallery, video, audio, or quote |
gallery, video, audio, quote | varies | Optional media for the matching format; sticky is an optional boolean |
Error Handling
try {
const res = await fetch(apiUrl);
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const data = await res.json();
} catch (err) {
// show a fallback UI state instead of a blank page
console.error(err);
}
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. In Revolution these values come from each page’s data file (the page export), blog posts use their title and excerpt, and project pages use the title and description in portfolio-projects.js:
<Layout title={contactPage.title} description={contactPage.description}>
<!-- contactPage (src/data/contact.js):
title: 'Contact – Revolution – Agency'
description: 'We’d love to hear from you! …' -->
Add an image prop (a root-relative path such as /images/home/hero-bg.jpg) to give a page its own Open Graph image.
Canonical URL, Open Graph & Twitter/X Cards
Already set inside Layout.astro’s <head>, using the site value from astro.config.mjs to build absolute URLs. og:image is only output when the page passes an image prop (blog posts and project pages do), and is turned into an absolute URL automatically:
{canonical && <link rel="canonical" href={canonical} />}
<meta property="og:type" content="website" />
<meta property="og:site_name" content={site.name} />
<meta property="og:title" content={title} />
{description && <meta property="og:description" content={description} />}
{ogImage && <meta property="og:image" content={ogImage} />}
<meta name="twitter:card" content="summary_large_image" />
{noindex && <meta name="robots" content="noindex" />}
Sitemap & Robots.txt
Astro’s official sitemap integration (@astrojs/sitemap, already installed and registered in astro.config.mjs) generates sitemap-index.xml automatically at build time once the site value is set. Revolution doesn’t ship a robots.txt; to add one, create public/robots.txt pointing to that sitemap:
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap-index.xml
Structured Data & Semantic HTML
Use landmark elements (<header>, <nav>, <main>, <article>) and a single <h1> per page so both search engines and screen readers can parse the page’s structure. Revolution ships no JSON-LD structured data; add it in Layout.astro’s <head> for rich results (e.g. Organization schema site-wide, or Article schema in PostLayout.astro) where relevant to your content.
Deployment
Platform-by-platform steps. Confirm your rendering mode first — it changes the build output you deploy.
A static Astro build (the default) produces plain HTML/CSS/JS in dist/ that any static host can serve. An Astro site using server-side rendering (SSR via an adapter) needs a Node-capable host or the platform’s matching adapter. Revolution is fully static — no adapter is installed — so the static instructions below apply. Using the wrong instructions for your rendering mode is the most common deployment mistake.
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(Astro static) — Vercel’s Astro preset detects this automatically in most cases. - Revolution needs no environment variables; if you’ve added any in a
.envfile, set them under Project Settings → Environment Variables. - Click Deploy.
Netlify
[build]
command = "npm run build"
publish = "dist"
Connect the repository in the Netlify dashboard, confirm these build settings, add environment variables under Site configuration → Environment variables, and deploy.
Cloudflare Pages
Connect the repository, set the build command to npm run build and the output directory to dist. For SSR Astro sites, use the official @astrojs/cloudflare adapter instead of the static preset.
GitHub Pages
Set site and base 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). GitHub Pages only serves static output — it cannot run an SSR adapter.
Traditional Hosting
For a static build, upload the contents of dist/ to your host’s public web root via FTP/SFTP or your host’s file manager. For an SSR build (if you later add an adapter), the host must support running node continuously (a VPS or a Node-compatible host) — plain shared hosting without Node support cannot run it.
Build & Production
What happens when you run build, and a checklist before you ship.
npm run build # render every page, bundle, and write dist/
npm run preview # serve dist/ locally, exactly as it will run in production
Build Output
The build writes one static HTML file per route, hashed CSS/JS bundles, the bundled font files, sitemap-index.xml, and everything from public/ (copied as-is) 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
Revolution uses none out of the box. If you add some, note that variables from your local .env are not deployed automatically — each hosting platform requires you to set them again in its dashboard (see Deployment). A missing production variable is one of the most common causes of a build that works locally but fails, or behaves unexpectedly, once deployed.
Production Checklist
- Run
npm run buildlocally and confirm it completes with no errors. - Run
npm run previewand click through the main pages. - Confirm the
siteURL inastro.config.mjsmatches your real domain. - If you added a
.envfile, set each of its variables in your hosting platform’s dashboard. - Replace the placeholder
https://formspree.io/f/your-form-idinsrc/data/contact.jsandsrc/data/comments.jswith a real endpoint, and send a test message. - Replace the demo phone and email in
src/data/site.js, the social URLs innavigation.js/footer.js, and the copyright line infooter.js. - Verify the favicon, logo, Open Graph images, and meta descriptions are your own, not the template defaults.
- Replace demo images and blog posts you don’t have the rights to use.
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 (often after moving a component to a different folder). Revolution defines no path aliases.
- Solution
- Re-run
npm install; check the import path casing matches the file exactly (Linux build servers are case-sensitive) and that the number of../segments matches the file’s new location.
Images not loading
- Cause
- A path that doesn’t match the file in
public/images/(wrong folder, case, or extension), or a path that includespublic/.
- Solution
- Review Images & Assets —
public/files are referenced with a root-relative URL that leaves outpublic, like/images/home/hero-bg.jpg.
Styles not loading
- Cause
- A new page doesn’t render inside
Layout(which importsglobal.css), or a style in a component’s scoped<style>block is being targeted at markup from a different component.
- Solution
- Wrap new pages in
Layoutfromsrc/layouts/Layout.astro; put styles in the same.astrofile as the markup they target, or inglobal.cssif they’re shared.
Blog post front matter errors
- Cause
- A post’s front matter doesn’t match the schema in
src/content.config.js— a missingtitle,slug, ordate, aformatother than the five allowed values, orcategories/tagswritten as a string instead of a list.
- Solution
- The error names the file and field; compare it with an existing post (see Content Editing) and fix the front matter.
Build fails (but dev server works)
- Cause
- The dev server only renders the pages you open, while
buildrenders every route — an error on a page you never visited in dev (a project page, a blog archive) only shows up in 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
- Only applies if you’ve added your own — Revolution reads none. Usually a missing
PUBLIC_prefix for a client-side value, or the dev server wasn’t 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, a Node.js version older than 22.12.0 on the build server, or (if you added any) environment variables set locally but not in the platform’s dashboard.
- Solution
- Re-check the build command and output folder against Deployment for your platform, set the platform’s Node.js version to 22 or later, and confirm any
.envvariables are duplicated there.
FAQ
Quick answers to the questions we hear most.
How do I install the template?
Extract revolution-astro-package.zip, open the revolution-astro/ folder, run npm install, then npm run dev. The full walkthrough is in Getting Started.
Which Node.js version should I use?
Node 22.12.0 or later — see Requirements. The current LTS release is a safe default.
Can I use JavaScript instead of TypeScript?
Revolution is already plain JavaScript — there are no .ts files and no type annotations to remove. The tsconfig.json is only Astro’s default editor configuration. You can add TypeScript later if you want it; Astro supports it without extra setup.
How do I change colors?
Update the --color-* custom properties at the top of src/styles/global.css — e.g. --color-accent for the green hover fill — see Styling. A few one-off colors (like the blog post background) are passed as props or set in a component’s own <style> block.
How do I change fonts?
Swap the Fontsource import or the Satoshi @font-face files, and update --font-heading / --font-body in global.css — see Fonts & Icons.
How do I replace images?
Swap the file in public/images/ or update its path in the matching src/data/ file or post front matter — see Images & Assets.
How do I add a new page?
Add a file to src/pages/; the route is created automatically — see Pages. New blog posts go in src/content/blog/ instead, and new portfolio projects in src/data/portfolio.js and portfolio-projects.js.
How do I deploy the template?
Platform-specific steps for Vercel, Netlify, Cloudflare Pages, GitHub Pages, and traditional hosts are in Deployment.
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: home, about, service, contact, and 404 pages.
- AddedSeven portfolio grid layouts, portfolio category pages, and 12 single-project page variants.
- AddedMarkdown blog with five post formats, pagination, and category, tag, author, and format archives.
- AddedGSAP animations, Swiper sliders, self-hosted fonts, and an automatic sitemap.
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