Skip to content
AstroCraft Docs
On this theme

Configuration

Urbic keeps its copy and its settings in typed modules under src/config/, never as literals in components. That is not tidiness for its own sake — it is what lets the CMS’s page composer read a section’s props and draw a control for them, and what lets a cross-file check fail the build when two lists that describe the same four things drift apart.

The site config

Fourteen data modules live in src/config/, and eleven of them ship a sibling *.test.ts that fails the build on a mistake a type cannot catch. The ones you will touch first:

siteData.json.ts is the brand. name, title, description, the author block, defaultImage, the sameAs social URLs that disambiguate the JSON-LD Organization, a tagline, and a four-field contact block — phone, email, address lines and opening hours. That contact block is read in three places at once (the header panel, the footer identity row and the contact page’s studio pairs), so changing it once changes all three. The values that ship are the mock’s invented Copenhagen studio.

siteSettings.json.ts is small and worth knowing by heart. siteLang sets <html lang>; siteLocale is the BCP-47 tag that feeds Intl date formatting, og:locale and the JSON-LD inLanguage. Then three switches: useViewTransitions, useAnimations (the decorative motion layer — scroll reveals and the rest), and useSmoothScroll. Turning the last one off means BaseLayout emits no <script> tag at all, so no page fetches Lenis. prefers-reduced-motion is honoured regardless of any of them.

navData.json.ts is the header’s single source of links, with children on the Services entry rendering as the flyout panel. footerData.json.ts and legalData.json.ts do the same job for the footer and for the terms and privacy copy. The legal copy is a placeholder — have it reviewed.

The rest — homeData, aboutData, aboutPageData, blogData, contactData, faqData, partnersData, projectsData, servicesData, testimonialsData, workData — carry the copy their pages need. Two of them are shaped deliberately: servicesData.json.ts holds each service as what a src/data/services/<slug>/ frontmatter would carry, so the day a services collection lands the section swaps an array for getCollection and the card does not change; and projectsData.json.ts hand-picks six of the nine case studies for the homepage band while /work/ reads the collection.

Types live in src/config/types/configDataTypes.ts. SiteHref is the one to know: it types every internal href as trailing-slashed, which is what keeps canonical URLs, the sitemap and the nav agreeing on one URL shape.

The cross-file checks are the interesting part. navData.test.ts, footerData.test.ts and servicesData.test.ts each guard a pair of lists that state the same fact twice — the four service titles appear in the nav flyout and on the service entries, because an entry needs a photograph the nav has no business carrying. The test fails the build if they drift.

src/admin.config.ts

This is the one file adopting the CMS asks you to write, and it deliberately sits outside src/admin/ — a file you write cannot ship inside the thing you are updating.

It states four kinds of fact. appName and workspace name the install; workspace.domain is imported from src/site.domain.ts rather than typed, because Settings → Site rewrites and commits that file when an operator connects a domain, and a literal here would stop tracking it. git names the remote and the publish branch — no repository URL, because the checkout already knows its own.

collections maps each content collection onto the roles the editor needs: which field is the title, which is the date, which is the description, which is the social image, and optionally which are the draft and updated flags. The rule that makes this safe is that it can only ever name fields that already exist in src/content.config.ts. It cannot declare a field and it cannot disagree with the Zod schema about a type, so there is no second schema to drift from the first. How the schema becomes the admin explains what each Zod type turns into on screen.

Urbic maps all three collections. blog becomes “Journal” at /blog/, projects becomes “Work” at /work/, and authors becomes “People” with no publicPath and no dateField — that schema has neither a public address nor a date, and naming a field that does not exist is the one thing this file may not do. The list row falls back to the slug and the editor draws no Preview button.

One choice there is worth copying if you add a collection: projects uses completed (a string like “February 2025”) as its dateField, not year. dateField is parsed with new Date(value), so the number 2025 would read as two seconds past the epoch and sort every project into 1970.

components points the page composer at src/components/Sections — capital S, because the composer walks that path and a case-sensitive filesystem finds nothing under the lowercase spelling. overrides then declares what the prop walker cannot read on its own: BaseLayout’s three SEO roles, three chrome components that take no content at all (props: [] says “nothing to edit here”, which reads differently from “nobody has told the CMS what this takes”), and two components whose props exist but cannot be authored from a page — the pager’s resolved neighbours and the enquiry field’s option list, which is also the server’s allow-list. Those are listed, locked and round-tripped untouched. See The component library.

Environment variables

Nothing below is required to run pnpm dev. .env.example documents all of them.

The site. SITE_URL is your production domain and feeds canonical URLs, OG tags, JSON-LD, the sitemap, robots.txt and llms.txt. A production deploy throws rather than ship the example.com placeholder; the gate at the top of astro.config.mjs reads Netlify’s CONTEXT and Vercel’s VERCEL_ENV natively, and DEPLOY_ENV=production anywhere else. Set that one only in production, or preview deploys start failing too.

The enquiry form. RESEND_API_KEY and CONTACT_TO_EMAIL, both optional; CONTACT_FROM_EMAIL defaults to Resend’s shared sandbox sender so a first test send works before you verify a domain. Without the first two the form renders and validates, and the handler answers with a readable “not configured yet” message. See Contact Form.

The CMS. MAIL_FROM is the sender for team invites and sign-up verification. It is optional as a value but structurally required — the admin’s actions import it from astro:env/server, so deleting the line from the env schema does not warn, it kills the build with [MISSING_EXPORT] "MAIL_FROM" is not exported by "\0astro:env/server".

ASTROCRAFT_DB_URL and ASTROCRAFT_DB_TOKEN point the CMS at a libSQL database for accounts, sessions, settings, drafts and invite tokens. Unset, @astrojs/node installs its filesystem session default and the CMS uses its own local store. Set, sessions become rows in the same database as the accounts, so a redeploy stops logging everybody out. This is read at config load, which means it must be set for the build as well as for the running server. Bring a database covers the options; Environment variables is the full list.

ASTROCRAFT_PUBLISH_OUT_DIR redirects where Publish’s build gate writes, so a publish does not overwrite the dist/ the running process is serving from.

Theme tokens

Colours, type and radii are CSS-first, in src/styles/tailwind-theme.css and src/styles/global.css, not in a JS config object. Colors and Typography cover the three-layer token architecture and what to edit for a rebrand.

NEXT STEPDeployment