Louisa 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 Louisa, a bold, editorial portfolio template for designers, studios and creative freelancers. Oversized typography, a warm cream-and-olive palette and carefully tuned motion put your work at the centre, while the codebase stays small, static and easy to edit. 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
Not shipped pre-built — run npm run build to generate a deployable dist/ folder in a few seconds (see Build & Preview).
Design assets
Placeholder photography in public/images/ (WebP originals with smaller JPEG sizes for responsive srcset), project thumbnails and detail images in public/images/projects/, SVG icons and decorative graphics in public/images/, and the self-hosted General Sans variable font in public/fonts/. No Figma or other design source files are included.
This documentation
A single offline HTML file — open it directly in any browser, no server required.
Features
- Five page types: Home, About, Contact, a Project detail page for every project, and a custom 404
- A project collection driven by one data file — add an entry and it appears on the home grid with its own detail page and prev/next navigation
- GSAP + ScrollTrigger motion: scroll reveals, masked headline slide-ins, a portrait “shutter” reveal, a scroll-driven services image reel, an infinite “Approach” marquee, a magnetic “Get in touch” button and a hide-on-scroll header — all of it respects
prefers-reduced-motion - A Swiper testimonials slider with a fade transition, looping, and keyboard and screen-reader support
- An accessible slide-in menu with
aria-expanded, Escape-to-close and visible focus states - SEO-ready: per-page titles and descriptions, canonical URLs, Open Graph / Twitter tags and an auto-generated sitemap
- Plain CSS with design tokens (CSS custom properties) — no CSS framework to learn
Technology Stack
Louisa is a static Astro site written in plain JavaScript and CSS — no React, TypeScript, Tailwind or Sass.
Requirements
| Tool | Minimum version | Notes |
|---|---|---|
| Node.js | 22.12.0 or later | Required by Astro 7 (set in package.json → engines) |
| Package manager | npm 9+, pnpm 8+, or Yarn 1.22+ | The template ships an npm package-lock.json; pnpm and Yarn work too |
| 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, and the template ships no polyfills.
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 louisa-portfolio-astro-package.zip
cd louisa-portfolio-astro-package/louisa-portfolio-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 (built on Vite) 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 renders every page (including one page per project), bundles and minifies the CSS and JavaScript, 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 a missing import or a broken project entry) that the dev server only reports when you happen to open the affected page.
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. Every image and the General Sans font live here and are referenced by root-relative URLs like /images/Star.svg. See Images & Assets. |
src/components/ | Shared UI (Navbar, SmartNavbar, Footer, AnimatedLink, CtaLink, ContactLinks, ProfileComponent) at the top level, and page sections grouped into about/, contact/, home/ and project/. |
src/layouts/ | BaseLayout.astro defines the common page shell — stylesheets, <head> and SEO tags, the hide-on-scroll navbar and the footer — so individual pages only supply their unique content. |
src/pages/ | Every file here becomes a route automatically. See Pages for details. |
src/scripts/ | animation.js registers GSAP and ScrollTrigger and exports shared helpers; interactions.js runs the site-wide scroll reveals and link hover effects; nav.js works out the current page for nav links; arrowLink.js drives the round-arrow hover on the project Prev/Next and “Back to all work” links. |
src/styles/ | Four plain-CSS stylesheets — normalize.css, base-framework.css, global.css (the template’s styles and design tokens) and motion.css. See Styling. |
src/data/ | site.js (name, contact details, default SEO text, nav and social links) and projects.js (the project collection), kept separate from markup so they’re easy to edit without touching Astro templates. |
LICENSE (the commercial license), package-lock.json (npm lockfile), .gitignore, and .vscode/ (recommended Astro extension and a launch config). Generated folders — node_modules/, .astro/ and dist/ — appear after you install and build, and are ignored by Git.
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": "louisa-portfolio-astro",
"type": "module",
"version": "1.0.0",
"engines": { "node": ">=22.12.0" },
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/sitemap": "^3.7.4",
"astro": "^7.3.4",
"gsap": "^3.15.0",
"swiper": "^14.2.0"
}
}
astro.config.mjs
Controls integrations, the deployed site URL, and build behavior. Louisa registers a single integration, the sitemap. Replace site with your production URL before you deploy — it’s used for canonical links, Open Graph tags and the sitemap.
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
// Replace with your production URL — used for canonical URLs, OG tags and the sitemap.
site: 'https://example.com',
integrations: [sitemap()],
});
tsconfig.json
The template is written in plain JavaScript, so this file only gives your editor Astro’s type information for autocomplete. It extends Astro’s base preset and defines no path aliases — imports use ordinary relative paths such as ../components/Footer.astro.
{
"extends": "astro/tsconfigs/base",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist", "_starter"]
}
Environment Variables (.env)
Louisa doesn’t read any environment variables, so no .env file is needed to run or build it. If you later add values that differ between environments (API URLs, keys) or shouldn’t be committed to Git, create a .env file in the project root. Variables must be prefixed PUBLIC_ to be readable in client-side code. For example:
# 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 how the project pages are generated.
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. Louisa ships these routes:
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/contact.astro → /contact
src/pages/project/[slug].astro → /project/:slug (one page per project)
src/pages/404.astro → shown for any unmatched URL
To add a page, create a new file in src/pages/ using an existing, similar page as a starting point. The Home and Contact pages also render <Navbar />, the large static header shown on desktop (project pages use <ProjectNavbar /> instead):
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Navbar from '../components/Navbar.astro';
import { site } from '../data/site.js';
---
<BaseLayout title={`Services | ${site.name}`} description="What I can do for you.">
<Navbar />
<main>
<h1>Services</h1>
</main>
</BaseLayout>
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. Louisa uses this for its projects: getStaticPaths() builds one page per entry in src/data/projects.js and hands each page its previous and next project, wrapping around at the ends:
---
import { projects } from '../../data/projects.js';
export function getStaticPaths() {
return projects.map((project, index) => ({
params: { slug: project.slug },
props: {
project,
prev: projects[(index - 1 + projects.length) % projects.length],
next: projects[(index + 1) % projects.length],
},
}));
}
---
You don’t need to touch this file to add a project — add an entry to projects.js instead (see Content Editing).
Layouts
Louisa has one layout, src/layouts/BaseLayout.astro. It imports the four stylesheets, writes the <head> (title, description, canonical URL, Open Graph and Twitter tags, fonts), renders the hide-on-scroll SmartNavbar and the Footer, and injects the page through a <slot />. Every prop is optional and falls back to the values in src/data/site.js:
---
const {
title = site.title,
description = site.description,
image = site.ogImage, // Open Graph / Twitter image
showFooter = true,
showSmartNav = true,
bodyClass, // e.g. "dark-body" on project pages
} = Astro.props;
---
<html lang="en">
<head><title>{title}</title> …</head>
<body class={bodyClass}>
{showSmartNav && <SmartNavbar />}
<slot /> <!-- page content is injected here -->
{showFooter && <Footer />}
</body>
</html>
Page Metadata & the 404 Page
Pass a title and description into BaseLayout on every page for correct browser tabs and search results (see also SEO); the Home page uses the defaults from site.js. src/pages/404.astro is served automatically whenever no route matches — it passes showFooter={false} and showSmartNav={false} for a clean, centred error page. Project pages pass bodyClass="dark-body", which switches the page to the dark background and keeps the mobile header’s logo and menu light until you scroll.
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. Every component is a plain .astro file:
| Component | Purpose |
|---|---|
Navbar.astro | Large static header with the name and nav links (desktop only; hidden at ≤991px) |
SmartNavbar.astro | Hide-on-scroll header with the slide-in menu, on every page except the 404 via BaseLayout |
Footer.astro | Socials, contact links, “Get in touch” button, name, copyright and Work/About links |
AnimatedLink.astro | A link with the hover underline that sweeps in and out |
CtaLink.astro | “Browse all work”-style link: a dot that grows into an arrow button on hover |
ContactLinks.astro | Phone and email links from site.js, in a white (menu, footer), dark (header) or lite (dark project-page header) variant |
ProfileComponent.astro | Portrait with the social icon links, used in the Home, About and Contact heroes |
home/ | HomeHero, SelectedWork (the project grid), Testimonials |
about/ | AboutHero, Services, Approach (marquee), Cta |
contact/ | ContactSection (the contact form) |
project/ | ProjectNavbar (desktop header with “Back to all work” and light nav links), LiveSiteLink (magnetic “Live site” button), ProjectNav (prev/next links) |
Props
Props are the inputs a component accepts, similar to HTML attributes. Louisa’s components read them by destructuring Astro.props, with defaults for anything optional. For example, CtaLink:
---
// variant "dark" (on light backgrounds) or "white" (on dark backgrounds).
const { href = '/#work', label = 'Browse all work', variant = 'dark' } = Astro.props;
---
In plain language: every prop is optional. Leave them all out and you get a dark “Browse all work” link to the project grid; set href and label to point it elsewhere, and variant="white" when it sits on a dark background. AnimatedLink works the same way with href, class, lineClass ("white-bg" for a white underline), current and external.
Component Composition
Pages are composed from section components rather than written as one long block of markup. The whole Home page is:
<BaseLayout>
<Navbar />
<main>
<HomeHero />
<SelectedWork />
<Testimonials />
</main>
</BaseLayout>
Passing Data to Components
In Astro, props are passed the same way as HTML attributes and read via Astro.props. The 404 page reuses CtaLink like this:
---
import CtaLink from '../components/CtaLink.astro';
---
<CtaLink href="/" label="Back to home" />
There are no framework islands. Each interactive component (the menu, the testimonials slider, the services image reel, the magnetic buttons) carries its own small <script> that Astro bundles, using GSAP or Swiper from npm. Site-wide effects such as scroll reveals and link underlines run from src/scripts/interactions.js.
Styling
Where to change colors, type, and spacing without fighting the build.
Global Styles & CSS Variables
The template uses plain CSS — no Tailwind, Sass, or CSS-in-JS. BaseLayout.astro imports four stylesheets in order: normalize.css, base-framework.css (containers, grid, forms and nav basics), global.css (the template itself) and motion.css (the hidden starting states for animations, and reduced-motion overrides). The color palette and font families are defined once as CSS custom properties at the top of global.css and referenced everywhere else. Change a value here and it updates across the whole site.
:root {
--color--cream-yellow: #feeec6;
--fonts--para: "Generalsans Variable", sans-serif;
--color--dark: black;
--fonts--heading: "Plus Jakarta Sans", sans-serif;
--color--grey: #454545;
--color--yellow: #fbc53c;
--color--heading: #151515;
--color--green: #445635;
--color--white: #fff;
}
Component Styles
Most styling lives in global.css, but Astro components also support scoped <style> blocks — CSS written there only applies to that component, so class names never collide across files. Louisa uses them for small, component-only tweaks (for example the slider arrows in Testimonials.astro).
<style>
.slider-arrow-icon {
display: block;
width: 100%;
height: 100%;
}
</style>
Responsive Design, Typography & Spacing
Styles are written desktop-first. global.css overrides them in media-query blocks after the base rules — max-width: 991px (tablet), 767px (landscape phone) and 479px (phone) — where font sizes, spacing and layouts step down. Edit the matching rule in each block when you change a size, so the site keeps a consistent rhythm at every width.
The dist/ folder is generated output — any change made there is overwritten on the next build. Always edit files under src/.
Images & Assets
Where the images live, and how to swap them for your own.
| Location | Use for | Behavior |
|---|---|---|
public/images/ | Every photo, SVG icon and decorative graphic in the template | Copied as-is, no processing; referenced by URLs like /images/Hero-Image.webp |
public/images/projects/ | Project thumbnails (thumb-*.webp) and detail images (detail-*.webp) | Copied as-is; paths are set in src/data/projects.js |
src/assets/ (optional) | Images you want Astro to optimize at build time | Not used by the template; create it if you switch to Astro’s <Image /> component |
Replacing Images
The quickest route is to swap the file in place, keeping the same filename. To use a new filename, update the path where it’s referenced — project images in src/data/projects.js, the social-sharing image in src/data/site.js, and section images directly in the component markup:
{ slug: 'wallet-app-redesign', title: 'Wallet App Redesign', thumbnail: '/images/projects/thumb-1.webp', ...shared },
Many section images also list smaller JPEG sizes in a srcset (for example Hero-Image-p-500.jpg and Hero-Image-p-800.jpg next to Hero-Image.webp). When you replace one of these, either provide matching sizes or remove the srcset and sizes attributes so the browser doesn’t load the old picture.
Supported Formats & SVGs
Images are plain <img> tags, so any format browsers support works — the template ships WebP originals with JPEG fallback sizes. Decorative graphics such as Star.svg and Decorative.svg are referenced as <img> sources, while icons that need to inherit color (arrows, social icons) are written as inline <svg> using currentColor.
Image Optimization
Because everything lives in public/, the build copies images exactly as they are — nothing is resized or re-encoded. Compress photos (WebP at around 800px wide works well for most sections) before you add them to the project.
Keep the template’s pattern: put new images in public/images/ and reference them with a root-relative URL. If you’d rather have Astro optimize an image, move it to src/assets/, import it, and render it with <Image /> from astro:assets.
Fonts & Icons
Swapping typefaces and icon sets without touching every component.
Google Fonts
Headings use Plus Jakarta Sans, loaded from Google Fonts by a <link> tag in BaseLayout.astro’s <head>. To change it, replace that link with your chosen font’s embed code, then update --fonts--heading in global.css:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap"
/>
Local Fonts
Body text uses General Sans (from Fontshare), self-hosted as a single variable font at public/fonts/GeneralSans-Variable.ttf. It’s declared with @font-face at the very top of global.css, used through --fonts--para, and preloaded in BaseLayout.astro:
@font-face {
font-family: Generalsans Variable;
src: url('/fonts/GeneralSans-Variable.ttf') format("truetype");
font-weight: 200 700;
font-style: normal;
font-display: swap;
}
To swap it, drop your font file into public/fonts/, update the src and font-family here and in --fonts--para, and change the <link rel="preload"> path in BaseLayout.astro.
Icon Libraries
Louisa doesn’t use an icon library. Small icons — the arrows in CtaLink.astro and the testimonial slider, and the social icons in ProfileComponent.astro — are inline SVG written directly in those components. Decorative graphics (Star.svg, Decorative.svg, Scroll-Arrow.svg, Link-arrow.svg and others) are SVG files in public/images/.
Replacing & Adding Icons
To change an inline icon, replace its <svg> markup in the component; keep fill="currentColor" so it follows the surrounding text color. The social icons in ProfileComponent.astro are stored as a list of label, href and SVG path values at the top of the file — edit or add an entry there. For file-based graphics, replace the SVG in public/images/ or point the <img> at a new file.
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/, locate the text between the tags, and edit it directly:
<!-- before (src/components/home/HomeHero.astro) -->
<p class="paragraph">With a passion for pushing the boundaries and a commitment to excellence, we transform visions into reality.</p>
<div class="text-slide-in-1">Creative</div>
<div class="text-slide-in-2">Designer</div>
<!-- after -->
<p class="paragraph">Brand identities and websites for independent businesses.</p>
<div class="text-slide-in-1">Visual</div>
<div class="text-slide-in-2">Designer</div>
Keep the class names as they are — they drive the slide-in animations. Your name (shown in the header, footer and page titles) comes from name, firstName and lastName in src/data/site.js, so change it there rather than in the markup.
Navigation & Footer
Menu links (navLinks) and social profiles (socialLinks) are lists in src/data/site.js, so you edit each label and URL once and every header, menu and footer picks it up. The footer’s copyright line is copyright in the same file. See Navigation for the full pattern.
Projects & Testimonials
Repeating content is driven by data arrays rather than hand-written per card, so adding or removing an item is a matter of editing a list. Projects live in src/data/projects.js: each entry becomes a card on the home grid and a page at /project/[slug]. The demo projects share one set of details through a shared object — give each real project its own values:
{
slug: 'wallet-app-redesign', // URL: /project/wallet-app-redesign
title: 'Wallet App Redesign',
thumbnail: '/images/projects/thumb-1.webp',
type: 'Website Redesign',
client: 'Client Name',
category: 'Brand Website',
year: '2022 - 23',
liveUrl: 'https://example.com',
mainImage: '/images/projects/detail-main.webp',
images: ['/images/projects/detail-1.webp', '/images/projects/detail-2.webp', '/images/projects/detail-3.webp'],
body: [{ heading: 'Overview', text: '…' }],
},
The home grid puts the first half of the projects in the left column and the rest in the right. Testimonials are a list at the top of src/components/home/Testimonials.astro; the slider and its “1 / 5” counter adjust to however many entries you keep:
const testimonials = [
{
quote: '“ Working with Louis was an absolute pleasure from start to finish. …”',
name: 'Martin Smith',
role: 'Managing Director - Northwind Studio',
},
// …four more
];
Blog Content & Contact Information
Louisa doesn’t include a blog. Contact details live once in src/data/site.js: phone and email each have a label (the text shown) and an href (tel: / mailto:), and they feed the header, menu, footer and the email link under the contact form. The social icons beside the portrait are set separately in src/components/ProfileComponent.astro.
phone: { label: '+39 123 456 789', href: 'tel:+39123456789' },
email: { label: 'hell@site.com', href: 'mailto:hell@site.com' },
Forms
What ships out of the box, and what still needs a backend.
The contact form is fully styled and validated, but it isn’t wired to any service. Until you set an endpoint, submitting it only shows the success message — no message is sent anywhere.
Contact Forms
The form lives in src/components/contact/ContactSection.astro and has Name, Email and Message fields. Set formAction at the top of that file to your endpoint:
// Point this at your form backend (Formspree, Netlify Forms, a serverless
// function, …). While it is empty the form only shows the success message.
const formAction = '';
Once it’s set, the component’s script sends the fields as a POST with fetch (asking for a JSON response), shows the success message if the endpoint replies OK, and shows the error message otherwise. The submit button reads “Please wait…” while it’s sending.
Newsletter Forms
Louisa doesn’t include a newsletter form. If you add one, follow the same pattern as the contact form and point its action at your email provider’s form endpoint.
Form Validation
Validation uses standard HTML attributes, so the browser gives immediate feedback without a round trip to a server: the Email field is required and type="email"; Name is optional and capped at 256 characters, and Message at 5,000. Add required to any other field you want to enforce.
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
Louisa fetches no external data — read on only if you plan to add an API.
All content comes from local JavaScript files — src/data/site.js and src/data/projects.js — and is baked into static HTML at build time. The only network request the template makes is the contact form’s submission, once you configure it (see Forms). The patterns below are for when you connect a CMS or API yourself.
API Configuration
If you add an API, read its URLs and keys from environment variables (see Configuration) rather than hard-coding them, 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
If you move the projects to a CMS or API, return objects with the same fields the pages already read from src/data/projects.js — slug, title, thumbnail, type, client, category, year, liveUrl, mainImage, images (an array of URLs) and body (an array of { heading, text }) — and the components will work unchanged. See Content Editing for a full example entry.
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 the layout on every page — these drive both the browser tab and the snippet shown in search results. Pages that don’t pass them (the Home page) fall back to title and description in src/data/site.js. Project pages build theirs from each project’s title.
<BaseLayout
title={`About | ${site.name}`}
description="An accomplished designer with a strong foundation in the field — services, approach and the story behind the work."
>
Canonical URL, Open Graph & Twitter/X Cards
Already set up in BaseLayout.astro’s <head>, using the site value from astro.config.mjs to build absolute URLs. The sharing image defaults to ogImage in src/data/site.js (/images/Hero-Image.webp); pass an image prop to a page’s layout to override it:
<link rel="canonical" href={canonical} />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage} />
<meta property="og:url" content={canonical} />
<meta name="twitter:card" content="summary_large_image" />
Sitemap & Robots.txt
Louisa includes Astro’s official sitemap integration (@astrojs/sitemap), which generates sitemap-index.xml at build time from the site value, and BaseLayout links to it. The template doesn’t ship a robots.txt; to add one, create public/robots.txt pointing at your sitemap:
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap-index.xml
Structured Data & Semantic HTML
Every page wraps its content in <main>, with a single <h1>, so both search engines and screen readers can parse the page’s structure — keep that pattern when you add pages. The template ships no JSON-LD structured data; add it in BaseLayout.astro’s <head> for rich results (for example Person or Organization schema) if you want them.
Deployment
Platform-by-platform steps. Confirm your rendering mode first — it changes the build output you deploy.
Louisa is a fully static Astro build: npm run build produces plain HTML/CSS/JS in dist/ that any static host can serve, with no adapter and no environment variables needed. Only follow SSR-specific steps below if you’ve added an adapter yourself.
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. - Add any environment variables from your
.envfile 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. Louisa needs nothing more, so any plain shared hosting works. Only if you later add an SSR adapter must the host run node continuously (a VPS or a Node-compatible host).
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 static HTML for every page (one per route, plus one per project), hashed CSS/JS bundles, the files from public/ and sitemap-index.xml to dist/. This folder is disposable and regenerated by every build — it’s already listed in the template’s .gitignore.
Environment Variables in Production
The template needs none. If you add variables to a local .env, note that they 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. - Replace the demo name, contact details, copyright and social links in
src/data/site.js, and the social icon links inProfileComponent.astro. - Replace the demo projects in
src/data/projects.jsand the testimonials inTestimonials.astro. - Set
formActioninContactSection.astroso the contact form actually delivers messages. - If you added environment variables, set each one in your hosting platform’s dashboard.
- Verify favicon, Open Graph image, and 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 or wrong number of
../in a relative import path (the template uses no path aliases).
- Solution
- Re-run
npm install; check the import path is correct relative to the importing file and its casing matches the file exactly.
Images not loading
- Cause
- A path that doesn’t match a file in
public/, often a filename case mismatch that works on Windows/macOS but fails on Linux hosts, or a stalesrcsetstill pointing at an old file.
- Solution
- Review Images & Assets — files in
public/images/need a root-relative URL like/images/Star.svgwith exactly matching case; update or remove thesrcsetwhen you replace an image.
Styles not loading
- Cause
- A page that doesn’t render inside
BaseLayout(which imports all four stylesheets), or a rule placed in a component’s scoped<style>that you expected to apply elsewhere.
- Solution
- Wrap the page in
<BaseLayout>and confirm its imports ofnormalize.css,base-framework.css,global.cssandmotion.cssare intact; move site-wide rules intoglobal.css.
Animations don’t play
- Cause
- Your operating system has “reduce motion” turned on (the template then shows content without animating it), or a JavaScript error earlier on the page stopped the scripts.
- Solution
- Check your system’s accessibility motion setting, then open the browser console and fix the first error listed. If you renamed classes in the markup, keep the animation classes (such as
text-slide-in-1) that the scripts look for.
Build fails (but dev server works)
- Cause
- The dev server only renders the pages you open, while
buildrenders all of them — including every/project/page — so a broken project entry or import on a page you didn’t visit only fails at build time.
- 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
- Missing the
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, or 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, and confirm every variable from
.envis duplicated there.
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, as required by Astro 7 — see Requirements.
Can I use JavaScript instead of TypeScript?
Louisa is already plain JavaScript — there are no .ts files to convert. tsconfig.json is only there for editor autocomplete.
How do I change colors?
Update the CSS custom properties at the top of src/styles/global.css — see Styling.
How do I change fonts?
Swap the Google Fonts link or the self-hosted font file and update --fonts--heading or --fonts--para — see Fonts & Icons.
How do I replace images?
Swap the file in public/images/ or update its 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?
Yes. The commercial license in the LICENSE file lets you build an unlimited number of websites with Louisa, for yourself or for clients (including commercial sites), modify it freely, charge clients for your work, and hand finished sites over to them. You may not resell, redistribute or give away the template itself (or a modified version) as a template, theme or starter, list it on a marketplace or public repository, or share your license or download link. The demo photos come from Unsplash and Pexels, Plus Jakarta Sans is from Google Fonts, and General Sans is from Fontshare — these are covered by their own licenses, so replace the placeholder content with your own work before publishing. See LICENSE for the full terms.
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, Contact, Project detail and 404 pages, the project collection, GSAP motion and the Swiper testimonials slider.
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