Skip to content
AstroCraft Docs
On this theme

Configuration

Urengi’s configuration is fifteen TypeScript modules under src/config/, each typed by an interface in src/config/types/configDataTypes.ts. They are TypeScript rather than JSON for one reason: a mistake becomes a build error naming the offending value, instead of a blank space on a page.

The split against component markup is consistent and worth internalising before you edit anything. A headline or a lead paragraph is editorial and stays in the section that shows it. Anything a buyer edits as a list — steps, tiers, objections, logos, quotes, matrix rows — lives in config, where a check can see it. And a note addressed to whoever is building the site, rather than to a visitor, lives beside the code it describes, not in config at all.

The five site-wide files

siteData.json.ts — brand name, the home page’s title, the site description, the author block, sameAs and the default social image.

Two of those fields have a wider blast radius than their names suggest. description is not only the home page’s: it is the site-wide WebSite JSON-LD description on every one of the 72 built pages and the lead line of /llms.txt, so a placeholder here is a placeholder in seventy-odd <head>s and in the machine-readable map. title, by contrast, has exactly one consumer — src/pages/index.astro — which is why it reads as a real page title rather than a bare brand word.

Anything the company does not own is left empty rather than invented, and the tags that would carry it are guarded so they simply do not render. author.twitter is dropped by BaseHead’s siteData.author.twitter && check, and sameAs stays [] in step with navData’s social hrefs. Fill those two together; they must name the same URLs.

siteSettings.json.tssiteLang (the <html lang> attribute), siteLocale (a BCP-47 tag feeding Intl date formatting, og:locale and JSON-LD inLanguage), and two switches:

export const siteSettings = {
  useViewTransitions: true,
  useAnimations: true,
} satisfies SiteSettingsProps;

satisfies rather than a type annotation is deliberate: it checks the shape while preserving the literal true, so a component branching on the flag keeps a narrowed type. useAnimations turns the decorative motion layer on and off site-wide; prefers-reduced-motion is always honored regardless, by a global guard in styles/motion/index.css. The flag is a design choice, not an accessibility one.

navData.json.ts — one source for the navbar (Sections/Global/Header) and the footer (Sections/Global/Footer), so the two surfaces cannot drift apart. It is the most load-bearing file in the config layer, and the Layout chapter covers how the chrome renders it. The important thing here is that its two halves answer different questions on purpose:

  • header is the built site. Every navbar destination resolves to a page that ships today, so the primary navigation never hands a visitor a 404. It holds one mega menu (Product, two titled columns of rich links), one plain dropdown (Company), three plain links and the sign-in link.
  • columns is the designed directory — the mock’s four footer columns, verbatim, running ahead of their routes.
  • cta is hoisted out of both because it is shown twice: the navbar renders primary only, the footer CTA band renders both.
  • social is the one group with no intended route at all; the three hrefs are "#" until you own the accounts.

navData.test.ts runs on every pnpm test. It fails on a malformed or duplicated href, and it prints the set of hrefs that have no page yet. The distinction matters: reporting rather than throwing is what lets the designed taxonomy ship ahead of its pages without the suite going red.

legalData.json.ts — the terms and privacy copy, section by section, rendered by the single Legal/LegalArticle section for both routes. It is placeholder text. Have it reviewed.

plannedRoutes.json.ts — the seventeen routes the design links but no page builds yet, exported as a plain array. astro.config.mjs turns it into redirects:

redirects: Object.fromEntries(plannedRoutes.map((route) => [route, "/"])),

This is not a catch-all — an unknown URL still gets the real 404 page. It is a shim over one specific gap: the footer directory, four job listings, four product sub-pages, the trust centre and three legal documents are all designed and linked but unbuilt, and in a live demo every one of them reads as a broken site rather than an unfinished one. Redirecting keeps both things true — the hrefs stay honest about the designed information architecture, and nothing dead-ends.

Build the page, then delete its entry here. Leaving a stale entry is the one dangerous mistake, because a redirect and a real page at the same route contradict each other; plannedRoutes.test.ts fails the moment a planned route has a page. Trimming the link out of its config file instead is equally fine — then delete the entry here too.

The nine per-page files

One file per page, each holding the rows that page draws: homeData, productData, pricingData, aboutData, contactData, authData, blogData, caseStudyData, integrationData.

Three of them also export the taxonomy their collection validates against, which is how a content typo becomes a build failure rather than a card silently missing from a filter page:

Export From Used by
CATEGORY_LABELS, POSTS_PER_PAGE blogData the blog schema, the tab row, the pager
INDUSTRY_LABELS, STORIES_PER_PAGE caseStudyData the caseStudies schema, the chips, the pager
CATEGORY_LABELS, FEATURED_COUNT integrationData the integrations schema, the filter bar, assertDirectory

Slugs are always derived from those labels by a slugify helper, never stored — so a link and the route it points at cannot disagree about what “Public sector” is called.

The checks a type cannot do

Eight config files carry a *.test.ts beside them. They exist because the interesting mistakes are all valid TypeScript:

  • An href that is well-typed but points at a route which does not ship.
  • A logo stem whose SVG is missing from src/assets/logos/ — the logoSvg lookup throws at build, and homeData.test.ts catches it earlier, on pnpm test.
  • A differentiator naming a graphic the section cannot render.
  • A duplicate pricing tier, or a matrix row whose cell count does not match the tier count.
  • A CTA edited to point somewhere other than /signup/, which would quietly orphan the most heavily advertised page on the site — authData.test.ts asserts it still lands there.

They run under Node’s type stripping with no framework, which is why every one of these files uses import type { … } rather than import { type … }. The distinction is real: with only type bindings, the second form leaves the whole import statement in place after stripping, so Node tries to resolve an extensionless specifier and fails.

Three places the config corrects its own design source

Worth knowing before you assume a value is a transcription error. Each is recorded in the theme’s wiki/design/ pages rather than applied silently.

Pricing. The Figma frame draws annual prices of €290 / €790 / €1,850 and a note reading “€3,480 billed yearly”, while promising twice that annual billing takes 20% off. But 290 × 12 = 3,480 exactly, so the discount is not applied anywhere in the frame. pricingData resolves it by reading those figures as the monthly list price — a pricing page never makes the discounted figure the round one — and deriving a clean 20% off for the annual rate.

The CEO’s name. The about frame names the co-founder “Marc Delhaye”. That name is already taken on this site by a customer: the home page’s only testimonial, a whole case study, and a portrait asset. Shipping the mock verbatim would make one man both the vendor and a customer endorsing it, and every check in the repo would be happy. The CEO’s surname changes instead; the shipped customer keeps his name, his story and his asset filename.

The contact page’s product. The contact frame sells a logistics platform, puts the company in San Francisco, and answers a setup question the home page already answers differently. All three are corrected to what the rest of the site says — vendor security reviews, Lyon, two days — because two pages of one site disagreeing is worse than one page differing from a placeholder.

Where colors and type are not

Colors, the type scale, spacing and motion are deliberately not in the config layer. They are CSS tokens in src/styles/tailwind-theme.css and src/styles/global.css. If you find yourself wanting to put a hex value in a config file, the answer is a token — see Colors & Theming.

NEXT STEPDeployment