Installation
Urengi 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, and nothing is hidden inside node_modules. 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, product, pricing, about, contact, a paginated blog, seven customer stories, a 24-entry integrations directory, sign-in and sign-up, and legal pages — populated with sample content. Configuration is what you do after you have looked around, not a prerequisite for seeing it work.
Requirements
- Node.js 22.13.0 or newer. This is enforced by
engines.nodeinpackage.json. The floor is set by pnpm 11 rather than by the language: the test runner executes TypeScript directly through Node’s--experimental-strip-types, which works from 22.12. Check yours withnode -v. - pnpm.
packageManagerpins[email protected], so with Corepack enabled the right version is selected for you.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 hosting account, an email provider, a CMS or a domain to run the theme locally. Urengi is a fully static site with no adapter — nothing renders on demand, which is why the local requirements stop here.
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/urengi.git my-saas
cd my-saas
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-saas.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 nine packages, and the shape of that list is the whole design philosophy of the theme in one place:
"dependencies": {
"@astrojs/mdx": "^7.0.0",
"@astrojs/sitemap": "^3.7.3",
"@fontsource-variable/host-grotesk": "^5.3.0",
"@tailwindcss/vite": "^4.3.2",
"astro": "^7.0.3",
"sharp": "^0.35.3",
"tailwind-merge": "^3.6.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.3.2"
}
Astro, Tailwind and its two class-composition helpers, one variable font, sharp for build-time image optimization, and two official Astro integrations. There is no UI kit, no animation library, no SEO package, no icon package, no schema package and no CMS. Those are all implemented inside the theme rather than pulled in — the 44 UI primitives, the 91-utility motion catalog, the JSON-LD builders, the 576-icon registry and the typed config layer are source files in src/. That is why the surface you have to keep up to date stays small, and it is also why customizing any of them means editing a file rather than reading a plugin’s options.
Two settings in pnpm-workspace.yaml are worth knowing about before they surprise you:
allowBuildsdenies post-install build scripts for@parcel/watcher,esbuildandsharp. None of the three needs to compile for the project to work, and denying them keeps installs fast and free of native toolchain requirements. If your install prints a notice about ignored build scripts, that is this setting working as designed.minimumReleaseAgeExcludepins the Tailwind packages to the exact version set the theme was built and tested against, including every platform-specific@tailwindcss/oxide-*binary.
Start the dev server
pnpm dev
The site is at http://localhost:4321. Every route is available immediately with sample content in place:
/— the home page/product/,/pricing/,/about/,/contact//blog/,/blog/page/<n>/,/blog/<slug>/,/blog/category/<slug>/— eight sample posts, four authors, four categories/customers/,/customers/page/<n>/,/customers/<slug>/,/customers/industry/<slug>/— seven customer stories across five industries/integrations/,/integrations/<slug>/,/integrations/category/<slug>/— a 24-entry directory in nine categories/signin/and/signup//privacy/and/terms/— placeholder legal copy/rss.xml,/robots.txt,/llms.txt,/sitemap-index.xml— all generated, all deriving their absolute URLs from one setting
Twenty-five route files produce 72 built pages, because seven of those files are dynamic: two paginate, three build one page per taxonomy label, and three build one page per collection entry. The full map is in Pages & Routing.
The primitive catalog
Before you start building pages, open http://localhost:4321/examples/ui. It is a development-only catalog rendering all 44 UI primitives in every variant, alongside the motion utilities and the full 576-icon registry, grouped into eight panels — Tier 1, Tier 2, Tier 3, the V2 batch, navigation, advanced form controls, icons and motion. It is the fastest way to find out what already exists rather than rebuilding it, and about twenty of the primitives are used by no page in the theme: that is inventory for the site you build next, and this is how you shop it.
The route is gated inside getStaticPaths:
export function getStaticPaths() {
return import.meta.env.PROD ? [] : [{ params: { catalog: "ui" } }];
}
A production build emits no paths for it, so no HTML ships. You can 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. With the catalog present the shared stylesheet is 118,821 bytes carrying 96 @keyframes; the site’s own pages reach for about nineteen of the catalog’s ninety-one animations. See Deployment for the full pre-launch list.
Environment variables
One variable matters, and it is not required locally. Copy the example file if you want a place to keep it:
cp .env.example .env
SITE_URL— your production domain. It feeds the canonical link, Open Graph tags, JSON-LD, the sitemap,robots.txt,llms.txtand the RSS feed, so one wrong value poisons six things at once and none of them looks broken in review. It defaults tohttps://example.com.DEPLOY_ENV— only needed on hosts that are neither Netlify nor Vercel. See below.- The Resend pairs —
RESEND_API_KEY,RESEND_AUDIENCE_IDand a*_FROM/*_TOpair per form. All optional, all unused until you connect a form. Forms & Email covers them.
A fresh clone builds and runs on the placeholder domain, deliberately, so you can see the site before you own a domain. What you cannot do is deploy on it — astro.config.mjs throws:
const site = process.env.SITE_URL ?? "https://example.com";
const isProductionDeploy =
process.env.CONTEXT === "production" || // Netlify
process.env.VERCEL_ENV === "production" || // Vercel
process.env.DEPLOY_ENV === "production"; // anything else
if (isProductionDeploy && site.includes("example.com")) {
throw new Error("SITE_URL is unset or still the placeholder. …");
}
The theme is host-agnostic, so “is this a production deploy?” reads each host’s own build variable rather than assuming one. None of those three is set by a local pnpm build or by a deploy preview, so previews and local work build freely. On a host not listed there, set DEPLOY_ENV=production yourself — the gate is only as good as the signal it can see, and an unset signal means the guard silently does nothing.
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 with the offending value named rather than shipping quietly.
src/config/siteData.json.ts— brand name, the home page’s title, the site description, the author block,sameAsand the default social image. The description is not only the home page’s: it is the site-wideWebSiteJSON-LD description on all 72 pages and the lead line of/llms.txt.src/config/navData.json.ts— one source for the navbar and the footer. Read the Configuration chapter before editing this one; the two surfaces answer deliberately different questions.src/config/siteSettings.json.ts—siteLang,siteLocale, and two switches:useViewTransitionsanduseAnimations(the master switch for decorative motion).prefers-reduced-motionis honored by a global CSS guard regardless of whatuseAnimationssays; the flag is a design choice, not an accessibility one.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, and it doubles as the JSON-LDOrganizationlogo until you pass a real one, so replacing it fixes two things.src/data/— replace the eight posts, four authors, seven customer stories and 24 integrations. Each entry is a folder whose name is the URL slug, holding anindex.md. Schemas live insrc/content.config.ts, so bad frontmatter fails the build with the entry named.
Colors, type and spacing are not in that list because they are not in the config layer. They are CSS tokens in src/styles/tailwind-theme.css and src/styles/global.css, and the Colors & Theming and Typography chapters cover them.
Verify your install
pnpm lint && pnpm check && pnpm build && pnpm test && pnpm wiki:lint
pnpm lintruns ESLint, including the Astro JSX accessibility rules.pnpm checkrunsastro checkacross.astroand.tsfiles.pnpm buildis the real check. Content-schema errors, config typos and broken references all surface here rather than in the browser.pnpm testruns every*.test.tsfile undersrc/with Node’s type stripping — no framework, no fixtures, nothing to register. There are twenty-six today. If it ever finds zero it fails rather than passing, so the suite cannot quietly disappear.pnpm wiki:lintdoes the same job for thewiki/knowledge base: it resolves everypath:linecitation and checks the cited line still contains the symbol the prose names.
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.
Two things to know before you start building
The theme follows the device, and a saved pick wins. BaseHead ships an inline, pre-paint script that reads localStorage("colorTheme"); if nothing is pinned it follows prefers-color-scheme and keeps following live OS changes. The ThemeToggle primitive is what writes that key. The script stays inline on purpose — moving it into a bundled <script> reintroduces a flash of the wrong theme.
Seventeen designed routes do not exist yet, and they redirect rather than 404. navData.columns ships the design’s complete footer directory, and aboutData, homeData and productData link careers, the trust centre and the product sub-pages. Every one of those hrefs is listed in src/config/plannedRoutes.json.ts and redirected to / by astro.config.mjs, so nothing on a live demo dead-ends. Build the page, then delete its entry — plannedRoutes.test.ts fails if a redirect and a real page ever claim the same route.
Troubleshooting
The build throws about SITE_URL. You are running a production deploy with the placeholder domain still in place. Set SITE_URL in your host’s environment variables. This is intentional — it makes it impossible to ship canonical URLs and a sitemap pointing at example.com.
A renamed content entry keeps 404ing in dev. Astro’s content layer caches entries, and moving or renaming a folder while the dev server runs 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.
Node version errors on install. The floor is 22.13.0. Older releases fail even if the install itself appears to succeed.
More in Troubleshooting.