Installation
Olsa ships as a complete Astro project, not as an npm package or an integration you register. You clone the repository, install its dependencies and start editing. There is no theme layer between you and the markup, nothing hidden in node_modules, and no build step you did not ask for. Every component, token, icon and animation is a file in src/ that you own from the first commit.
It runs with no configuration, no API keys and no accounts. pnpm install followed by pnpm dev gives you the whole site — home, features, pricing, about, blog, integrations, contact, sign-in, sign-up, legal pages and a 404 — populated with sample content. Configuration is what you do once you have looked around, not a prerequisite for seeing it work.
Requirements
- Node.js 22.12.0 or newer. Enforced by
engines.nodeinpackage.json, and it is not a soft floor: the check runner executes TypeScript directly through Node’s--experimental-strip-types, which only exists from 22.12 onward. Check yours withnode -v. - pnpm. The lockfile is
pnpm-lock.yaml, andpnpm-workspace.yamlcarries install settings the theme relies on. No pnpm version is pinned, so any recent release works.corepack enable pnpmis the shortest route if you do not have it. - Git, to clone the repository and to keep your own history from the first commit onward.
You do not need a host account, a Resend account or a domain to run the theme locally. Every one of those is optional and covered further down.
Get the code
Clone the repository and drop the upstream history so your project starts with a clean log:
git clone https://github.com/Astro-Craft-Theme/Olsa.git my-site
cd my-site
rm -rf .git && git init
If you would rather keep the upstream remote so you can pull theme updates later, skip the rm -rf .git and rename the remote instead:
git remote rename origin upstream
git remote add origin [email protected]:you/my-site.git
Keeping upstream is only worth it if you plan to merge theme changes. Because the theme is source you edit directly, a merge after heavy customization is a real merge — most people take the clean-slate route and treat the clone as a starting point.
Install dependencies
pnpm install
The runtime dependency list is deliberately short: Astro 7, Tailwind CSS v4 with its Vite plugin, tailwind-variants and tailwind-merge, one Fontsource variable font (Host Grotesk), and two official Astro integrations (@astrojs/mdx and @astrojs/sitemap). That is the whole list. There is no UI kit, no animation library, no SEO package, no icon package and no RSS package — those five things are implemented inside the theme rather than pulled in, which is why the surface you have to keep up to date stays small.
The dev dependencies are the tooling: @astrojs/check and TypeScript, ESLint with the Astro and jsx-a11y plugins, Prettier with the Astro and Tailwind plugins, and sharp for build-time image optimization.
Two details in pnpm-workspace.yaml are worth knowing before they surprise you:
allowBuildsdenies post-install build scripts foresbuildandsharp. Neither needs to compile for this project to work — both ship prebuilt binaries — and denying the scripts keeps installs fast and free of native toolchain requirements. If your install prints a notice about ignored build scripts, that is the setting working as designed, not a failure.minimumReleaseAgeExcludepins the Tailwind packages to the exact version set the theme was built and tested against, including the platform-specific@tailwindcss/oxide-*binaries.
Start the dev server
pnpm dev
The site is at http://localhost:4321. Every route is available immediately with the sample content in place:
/— the home page/features/,/pricing/,/about/,/contact//blog/and/blog/<slug>/— seven sample posts across six authors/integrations/and/integrations/<slug>/— nine sample integrations/signin/and/signup/— chrome-free auth pages, bothnoindex/terms/and/privacy/— placeholder legal copy/404//rss.xml,/robots.txt,/llms.txt,/sitemap-index.xml— all generated, all deriving their absolute URLs from one setting
The primitive catalog
Before you start building pages, open http://localhost:4321/examples/ui. It is a development-only catalog of all 44 UI primitives in every variant, alongside the 87 motion utilities and the 571-icon set. It is the fastest way to find out what already exists rather than rebuilding it.
The route is gated by import.meta.env.PROD inside getStaticPaths, so a production build emits no paths for it and no HTML ships. Leave it in place while you work. When you no longer need it, delete src/pages/examples/ and src/components/Sections/UiCatalog/ together — Tailwind still scans the catalog’s markup even though it builds no pages, so its demo classes sit in the stylesheet every real page loads.
What runs where
Every page prerenders to static HTML. There is no adapter in astro.config.mjs, deliberately: the theme builds to 100% static output and picks no host for you. pnpm build emits 28 HTML files and nothing that needs a server.
The one thing that would change that is the contact form, whose server half ships written and type-checked but unmounted. Connecting it means adding an adapter and turning exactly one route on-demand — see Contact Form. Until you do, nothing in the project needs a runtime.
This matters at install time for one reason: under astro dev everything runs on a server, so a route that accidentally depends on request-time behavior will work locally and fail in a static build. Run pnpm build early and often — it is the check that tells the truth.
Environment variables
None are required. A fresh clone builds and runs with an empty environment. The three variables that exist all belong to the contact form, all are optional, and all are server-only — declared in astro.config.mjs under env.schema with context: "server", so none of them can be bundled into a page.
cp .env.example .env
RESEND_API_KEY— a Resend API key. Required for the contact form to deliver mail.CONTACT_TO_EMAIL— where enquiries land. Also required for delivery.CONTACT_FROM_EMAIL— optional, defaults to[email protected]. That shared sandbox sender works immediately but can only deliver to the address that owns the Resend account, so ifCONTACT_TO_EMAILis a different address, delivery fails at the provider even though everything else is correct. Verify your own domain in Resend and set this to send anywhere.
Because all three are optional, a missing key is never a build failure. The action reports a readable message at request time instead — “This form is not configured yet” — which is a real error you can see rather than a silent drop.
Your production domain is not an environment variable here. It is the site field in astro.config.mjs, and it is the single most important thing to change before you deploy; see Deployment.
Make it yours
With the site running, these are the first files to edit. All of them are typed, so a mistake fails the build or astro check with the offending value named rather than shipping quietly.
astro.config.mjs— setsiteto your real domain. It still readshttps://example.com, and it feeds the canonical link,og:url, the sitemap,robots.txt,llms.txt, the RSS feed and the JSON-LD@ids. One value, seven consumers.src/config/siteData.json.ts— brand name, page title, description, the author block,sameAs(the social profile URLs that feed the Organization JSON-LD, shipped as[]), the publiccontactblock, anddemoVideoIdfor the “Watch demo” modal. The contact details ship as deliberate placeholders — a[email protected]inbox, a reserved-for-fiction555phone number and an Austin address from the mock.src/config/navData.json.ts— the header entries, the three footer link columns, the social row and the legal row. The header entries are all real routes; most footer column links are placeholder#hrefs waiting for pages you have not built yet.src/config/pricingData.json.tsandfaqData.json.ts— the plans, the comparison matrix and the FAQ list. All placeholder editorial from the design mock.src/config/legalData.json.ts— the terms and privacy copy. It is explicitly placeholder text. Have a professional review it; it is not legal advice.public/og.jpg,public/favicon.svg,public/favicon.ico— the shippedog.jpgis a placeholder. Replace it with a real 1200×630 image.src/data/— replace the seven blog posts, six authors and nine integrations. Schemas live insrc/content.config.ts, so bad frontmatter fails the build with the entry named.
Colors, type and spacing are not in this list because they are not in the config layer. They are CSS tokens in src/styles/tailwind-theme.css and src/styles/global.css — see Colors & Theming and Typography.
Verify your install
pnpm lint && pnpm check && pnpm build && pnpm test
pnpm lintruns ESLint, including the JSX accessibility rules.pnpm checkrunsastro checkfor types across.astroand.tsfiles. Some invariants in this theme are carried by types rather than runtime guards — the pricing comparison matrix is keyed by plan name, so a missing or misspelled cell is apnpm checkerror and not a build error. Do not skip it.pnpm buildis the real check for content and config. Schema errors, broken references and bad image paths all surface here.pnpm testruns every*.selfcheck.tsand*.test.tsundersrc/with Node’s type stripping — no framework, no fixtures, nothing to register. There are seven today.astro checktype-checks them without running them andastro buildignores them entirely, so this command is the only thing that executes them. If it ever finds zero files it fails rather than passing, so the suite cannot quietly disappear.
There is also pnpm format, which runs eslint --fix and then Prettier. Class ordering is handled by prettier-plugin-tailwindcss, so let it sort and do not hand-order class lists.
Troubleshooting
A renamed content entry keeps 404ing in dev. Astro’s content layer caches entries, and renaming a file while the dev server is running can leave the old entry in place and the new one missing. Restart pnpm dev; touching the file will not clear it.
A utility class has no effect in dev. In a long-running dev server, classes used only in newly created files can be missing from the generated stylesheet. Restart the dev server before you go looking for a bug in your markup.
pnpm test says no checks found. The runner walks src/ for *.selfcheck.ts and *.test.ts. If it reports zero it exits non-zero on purpose — you are running it from the wrong place, or the files moved.
Node version errors on install. The floor is 22.12.0. Older releases fail on the check runner even if the install itself appears to succeed.