Louisa Documentation

01 · Overview

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.

Astro 7 Vite GSAP + ScrollTrigger Swiper @astrojs/sitemap Plain CSS

Requirements

ToolMinimum versionNotes
Node.js22.12.0 or laterRequired by Astro 7 (set in package.json → engines)
Package managernpm 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
Git2.xOnly required if you plan to version-control your customized copy

Browser Support

The built site targets the last two versions of all evergreen browsers:

Chrome Firefox Safari Edge
Internet Explorer

Internet Explorer 11 is not supported, and the template ships no polyfills.

02 · Setup

Getting Started

From a downloaded ZIP to a running dev server in five steps.

Installation & Extraction

  1. Download the template ZIP from your purchase confirmation email or account downloads page.
  2. Extract it to a folder on your machine — avoid folder names with spaces or special characters.
  3. Open that folder in your terminal and in your code editor.
bash
# 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:

bash
node --version
# v22.12.0 or later

If you manage multiple Node versions, tools like nvm or fnm make switching painless:

bash
nvm install 22
nvm use 22

Install Dependencies

From the project root, install packages with your preferred package manager:

npm
npm install
pnpm
pnpm install
yarn
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
npm run dev
pnpm
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

bash
# 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.

Which command do I actually need?

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.

03 · Reference

Project Structure

A tour of the folders you’ll actually touch, and the ones you generally shouldn’t.

PathPurpose
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.
Files not shown above

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.

04 · Reference

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.

jsonpackage.json
{
  "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.

javascriptastro.config.mjs
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.

jsontsconfig.json
{
  "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:

env.env
# 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
Never commit .env

Add .env to .gitignore (it usually is by default). Anything without the PUBLIC_ prefix should stay off the client entirely.

05 · Guide

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:

text
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):

astrosrc/pages/services.astro
---
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:

astrosrc/pages/project/[slug].astro
---
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:

astrosrc/layouts/BaseLayout.astro
---
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.

06 · Guide

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:

ComponentPurpose
Navbar.astroLarge static header with the name and nav links (desktop only; hidden at ≤991px)
SmartNavbar.astroHide-on-scroll header with the slide-in menu, on every page except the 404 via BaseLayout
Footer.astroSocials, contact links, “Get in touch” button, name, copyright and Work/About links
AnimatedLink.astroA 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.astroPhone and email links from site.js, in a white (menu, footer), dark (header) or lite (dark project-page header) variant
ProfileComponent.astroPortrait 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:

astrosrc/components/CtaLink.astro
---
// 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:

astrosrc/pages/index.astro
<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:

astro
---
import CtaLink from '../components/CtaLink.astro';
---
<CtaLink href="/" label="Back to home" />
Where the interactivity lives

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.

07 · Guide

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.

csssrc/styles/global.css
: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).

astrosrc/components/home/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.

Avoid editing dist/

The dist/ folder is generated output — any change made there is overwritten on the next build. Always edit files under src/.

08 · Guide

Images & Assets

Where the images live, and how to swap them for your own.

LocationUse forBehavior
public/images/Every photo, SVG icon and decorative graphic in the templateCopied 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 timeNot 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:

javascriptsrc/data/projects.js
{ 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.

Rule of thumb

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.

09 · Guide

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:

htmlsrc/layouts/BaseLayout.astro
<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:

csssrc/styles/global.css
@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.

10 · Beginner Friendly

Content Editing

The fastest, lowest-risk way to make the site say what you want it to say.

New to code?

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:

astro
<!-- 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.

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:

javascriptsrc/data/projects.js
{
  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:

javascriptsrc/components/home/Testimonials.astro
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.

javascriptsrc/data/site.js
phone: { label: '+39 123 456 789', href: 'tel:+39123456789' },
email: { label: 'hell@site.com', href: 'mailto:hell@site.com' },
12 · Guide

Forms

What ships out of the box, and what still needs a backend.

Forms need somewhere to go

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:

astrosrc/components/contact/ContactSection.astro
// 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.

13 · Guide

API & Data

Louisa fetches no external data — read on only if you plan to add an API.

No API out of the box

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:

env.env
PUBLIC_API_URL=https://example.com/api
PUBLIC_API_KEY=your_api_key
Never expose real credentials

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

javascript
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

javascript
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);
}
14 · Guide

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.

astrosrc/pages/about.astro
<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:

astrosrc/layouts/BaseLayout.astro
<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:

textpublic/robots.txt
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.

15 · Guide

Deployment

Platform-by-platform steps. Confirm your rendering mode first — it changes the build output you deploy.

Static vs. SSR — check this first

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

  1. Push the project to a GitHub, GitLab, or Bitbucket repository.
  2. In Vercel, choose Add New → Project and import that repository.
  3. Build command: npm run build · Output directory: dist (Astro static) — Vercel’s Astro preset detects this automatically in most cases.
  4. Add any environment variables from your .env file under Project Settings → Environment Variables.
  5. Click Deploy.

Netlify

tomlnetlify.toml
[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).

16 · Guide

Build & Production

What happens when you run build, and a checklist before you ship.

bash
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 build locally and confirm it completes with no errors.
  • Run npm run preview and click through the main pages.
  • Confirm the site URL in astro.config.mjs matches your real domain.
  • Replace the demo name, contact details, copyright and social links in src/data/site.js, and the social icon links in ProfileComponent.astro.
  • Replace the demo projects in src/data/projects.js and the testimonials in Testimonials.astro.
  • Set formAction in ContactSection.astro so 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.
17 · Reference

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 --version meets the requirement in Requirements, then run rm -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 stale srcset still pointing at an old file.
Solution
Review Images & Assets — files in public/images/ need a root-relative URL like /images/Star.svg with exactly matching case; update or remove the srcset when 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 of normalize.css, base-framework.css, global.css and motion.css are intact; move site-wide rules into global.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 build renders 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 restart npm 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 .env is duplicated there.
18 · Reference

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.

19 · Reference

Changelog

Every notable change, newest first. Add a new entry above the last one for each release.

Version 1.0.0
  • AddedInitial release: Home, About, Contact, Project detail and 404 pages, the project collection, GSAP motion and the Swiper testimonials slider.
Keeping this current

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.

20 · Reference

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 top

Contact support

Email hello@rnnstudio.com or reach us through our contact form.

Contact support

Support 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:

text
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