Skip to content
AstroCraft Docs
On this theme

Configuration

8-BitQuest has no CMS and no integration options object. Its configuration is five TypeScript modules in src/config/, imported directly by the pages and sections that need them. They are typed by one shared file, src/config/types/configDataTypes.ts, so a missing field is a build error rather than an empty string in production.

They are named *.json.ts but imported without the extension:

import siteData from "@config/siteData.json";

The @config/* alias resolves to ./src/config/*, and TypeScript appends .ts. They are modules, not JSON — which is exactly why they can carry comments, derive one field from another, and be checked against an interface.

siteData.json.ts — who the site is

The identity layer: brand name, page title, description, the author block, the default social image, and the sameAs list. BaseHead, the footer and the JSON-LD builders all read from it.

const siteData = {
  name: "8-BitQuest",
  title: "8-BitQuest — retro pixel-art dev portfolio",
  description: "A retro 8-bit, pixel-art developer portfolio built on Astro 7 …",
  author: {
    name: "Your Name",
    email: "[email protected]",
    twitter: "yourhandle",
  },
  defaultImage: { src: "/og.jpg", alt: "8-BitQuest" },
  sameAs: [],
} satisfies SiteDataProps;

Four things about this file will save you time later.

  • author.twitter is the handle without the @. BaseHead renders it into the Twitter card attribution, so drop the leading @.
  • defaultImage.src is a public/ path, not a bundled import. It is the fallback OG image for any page that does not set its own. Replace public/og.jpg with a real 1200×630 image before launch.
  • sameAs feeds the Organization JSON-LD. It is the list of social/profile URLs that disambiguate your brand for search engines — ["https://x.com/yourhandle", "https://github.com/yourorg"]. Empty is fine; the array simply drops out of the structured data.
  • The file uses satisfies SiteDataProps, which checks the shape while keeping the literal types, so autocomplete on siteData.author.name stays precise.

portfolioData.json.ts — the copy a buyer makes their own

This is the file that turns the sample “Full-Stack Dev” persona into you. It holds only the facts a buyer is expected to customise — identity, biography, the experience and scoreboard numbers, the home intro, and the contact prompt — in one typed place. Presentational labels (the SYS_SPECS captions, “Role:” and “Yrs:”, the scoreboard colours) stay in their components, because they are design, not content.

const portfolioData = {
  profile: {
    tagline: "Dev 01",            // About-hero badge
    heading: "The Full-Stack Dev", // About-hero H1
    role: "Full-Stack",            // DevProfile ROLE value
    years: "8+",                   // DevProfile YRS value
    bio: ["Welcome to the mainframe. …", "…"], // one <p> per entry
    shortBio: "…",                 // home About-section bio
    meta: { location: "…", role: "…", favorite: "…" },
    skills: [ { label: "Frontend", pct: 95 }, { label: "Backend", pct: 90 } ],
  },
  stats: {
    home:    ["Posts: 42", "Years: 03", "Coffee: 9000+"],
    profile: ["Class: Full-Stack Dev", "Lvl: 8+", "XP: 8.5K", "Stars: 2.1K"],
  },
  home:    { tagline: "Player 1", heading: "Welcome, Player One", intro: "…" },
  contact: { prompt: "Want to chat about a project …" },
} satisfies PortfolioDataProps;

Two conventions to keep in mind. The skills bars render as HP-style meters, so pct is a 0–100 number that becomes the fill width. And the two stats arrays are display text only — each strip’s per-stat colour is defined in its component and pairs by array order, so keep the count and order in step with what the component expects. The voice is first-person singular throughout; keep it consistent if you rewrite.

siteSettings.json.ts — how the site behaves

Three named exports, no default export:

export const siteLang = "en" as const;
export const siteLocale = "en-US" as const;

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

siteLang becomes <html lang>. siteLocale is the BCP-47 tag used for Intl date formatting, og:locale and the JSON-LD inLanguage.

The two switches:

  • useViewTransitions gates <ClientRouter />. Off means no client-side navigation and no page-transition curtain. It pairs with vite.build.assetsInlineLimit: 0 in astro.config.mjs, which stops short scripts being inlined so they don’t break under the router.
  • useAnimations is the master switch for the decorative motion layer — the scroll reveals via <Reveal>, ambient loops. It does not touch intentional micro-interactions like a rotating accordion chevron, and it has nothing to do with accessibility: prefers-reduced-motion is honoured by a global CSS guard whatever this flag says. See Motion.

This is a single-language theme. The siteLang/siteLocale constants are the only locale facts in the project — the former i18n helper layer was removed, and its shape is recorded in the theme’s wiki/ if a project ever needs it back.

export const navItems = [
  { label: "About",    href: "/about/" },
  { label: "Projects", href: "/projects/" },
  { label: "Blog",     href: "/blog/" },
  { label: "Contact",  href: "/contact/" },
] as const satisfies readonly NavItemProps[];

Four links, Title-case in the data and uppercased in the UI by the Press Start 2P face. A nav item is routing and label only. The trailing slashes are load-bearing: the site is configured trailingSlash: "always", and the header decides which link is current by comparing this href against the pathname (@js/nav’s isActive). Home is intentionally not a nav item — the brand wordmark in the header is the home link, the common logo-as-home pattern.

legalData.json.ts — terms and privacy

One record per document, both required by the type, keyed terms and privacy:

{
  title: string;
  description: string;      // the page's meta description
  lastUpdated: string;      // ISO date, YYYY-MM-DD
  intro: string;
  sections: { heading: string; body: string[] }[];
}

Each body entry becomes one <p>. Both documents ship with placeholder sections and an explicit warning at the top of the file: this is template text, replace it with your own, reviewed by a qualified legal professional. The renderer (Sections/Legal/LegalArticle.astro) is deliberately simple — heading plus paragraphs, no nested lists or tables. If your legal documents need richer structure, promote legalData to a markdown content collection and render <Content /> in the section.

What is not in the config layer

Colours, type and spacing are not here. They are CSS custom properties in src/styles/tailwind-theme.css and src/styles/global.css — see Colors and Typography. Putting design tokens behind a JavaScript config would mean a build step between you and a colour change, for no gain.

Long-form content is not here either. Blog posts, projects and the author profile are content-collection entries under src/data/ — see Content Collections.

Verifying

Type errors in this layer surface with pnpm check, and everything is caught by the time you reach pnpm build. Config is not a trust boundary here — it is code you wrote — so Zod validation is reserved for data arriving from outside the type system: MDX frontmatter and contact-form input.

NEXT STEPDeployment