step 002 complete
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
## Context
|
||||
|
||||
Step 001 produced a working Astro project with Tailwind v4 brand tokens, Archia font-face declarations, and a smoke-test `index.astro` that manually constructs its own `<html>` document. Every subsequent page (homepage, about, AI Launchpad, contact, privacy — plus `/nl/*` variants) will need the same HTML shell with correct `<head>` meta, hreflang, OG tags, and body defaults. Without a shared layout this is copied per page, creating drift and SEO inconsistency.
|
||||
|
||||
Current state of note:
|
||||
- `global.css` is Tailwind v4 (`@import "tailwindcss"` + `@theme`) — no `tailwind.config.mjs`
|
||||
- Archia `@font-face` declarations already live in `global.css`
|
||||
- `public/` has `favicon.svg` and `favicon.ico`
|
||||
- No `src/layouts/`, `src/components/`, or `src/content/` directories yet
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Single `BaseLayout.astro` that every page wraps its content in
|
||||
- Correct SEO `<head>`: charset, viewport, favicon, font preloads, canonical, hreflang, OG, Twitter card
|
||||
- Body defaults applied once (Midnight bg, Snow text, Archia font)
|
||||
- JSON-LD injection mechanism (both prop and named slot)
|
||||
- Stub `Nav.astro` and `Footer.astro` so BaseLayout can import them (filled in steps 003+)
|
||||
- `en.json` / `nl.json` content files + `i18n.ts` loader for global meta fields
|
||||
- `index.astro` updated to use `BaseLayout`
|
||||
|
||||
**Non-Goals:**
|
||||
- Actual nav or footer UI (step 003)
|
||||
- Page-specific JSON-LD schemas (each page step handles its own)
|
||||
- OG image file creation (deferred — `ogImage` prop will be empty string initially)
|
||||
- Any client-side JavaScript
|
||||
- `@astrojs/image` or picture optimisation (images not needed in this step)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. hreflang: explicit props, not derived
|
||||
|
||||
**Decision**: `BaseLayout` accepts two explicit props — `canonicalUrl: string` and `alternateUrl: string`. The component does not compute the alternate URL.
|
||||
|
||||
**Rationale**: EN routes are `/about`, NL routes are `/nl/about` — a simple `/nl/` prefix works for most paths, but `/contact` vs `/nl/contact` or future edge cases (slugs, catch-alls) could diverge. Explicit props make each page's intent clear and avoid regex surprises. Callers always know their own routes.
|
||||
|
||||
**Alternative considered**: Auto-derive by prepending/stripping `/nl/` based on `locale` prop. Rejected: fragile for edge cases, magic at a distance.
|
||||
|
||||
### 2. Font preloads: critical weights only
|
||||
|
||||
**Decision**: Preload only Archia Regular (400), SemiBold (600), and Bold (700). Thin (100) and Light (300) load on demand.
|
||||
|
||||
**Rationale**: Each `<link rel="preload">` is an unconditional network request. Regular is needed for all body copy; SemiBold and Bold are used for headings and CTAs — the elements visible above the fold. Thin and Light appear only in specific decorative contexts (if at all) and are not worth the preload cost.
|
||||
|
||||
### 3. JSON-LD: prop for common case + named slot for extras
|
||||
|
||||
**Decision**: BaseLayout accepts an optional `jsonLd?: Record<string, unknown>` prop. If provided, it is serialized with `JSON.stringify` and injected as `<script type="application/ld+json" set:html={...}>`. A `<slot name="jsonld" />` is also provided for pages that need additional or multiple JSON-LD blocks.
|
||||
|
||||
**Rationale**: Most pages will pass one structured data object (e.g., `ProfessionalService`, `WebPage`). The prop keeps call sites clean. The named slot exists as an escape hatch for pages like the homepage that may need multiple JSON-LD entries.
|
||||
|
||||
### 4. Nav/Footer: imported stub components
|
||||
|
||||
**Decision**: `BaseLayout.astro` imports `Nav.astro` and `Footer.astro` directly as Astro components. Both are created as empty stubs (render nothing). Step 003 fills in `Nav.astro`; a later step fills `Footer.astro`.
|
||||
|
||||
**Alternative considered**: Named slots (`<slot name="nav" />`). Rejected: would require every page to manually import and wire up Nav, which is error-prone and defeats the purpose of a shared layout.
|
||||
|
||||
### 5. Body defaults: Tailwind classes on `<body>`
|
||||
|
||||
**Decision**: Apply `class="bg-midnight text-snow font-archia"` directly on the `<body>` element in BaseLayout. No `@layer base` rule added to `global.css`.
|
||||
|
||||
**Rationale**: These are layout-level defaults, not global resets. Keeping them as explicit Tailwind classes on the element makes them visible and overridable per-section without specificity surprises. Consistent with how `index.astro` already applies them.
|
||||
|
||||
### 6. Content JSON: flat `meta` key at top level
|
||||
|
||||
**Decision**: Both `en.json` and `nl.json` start with a top-level `"meta"` key containing `siteName`, `defaultTitle`, `defaultDescription`, and `defaultOgImage`. Subsequent steps add page-specific keys (`"home"`, `"about"`, etc.) at the same top level.
|
||||
|
||||
**Rationale**: Flat top-level keys by section keep the file scannable and allow each step to add its own section without touching others. The `"meta"` key is reserved for global/shared values.
|
||||
|
||||
### 7. i18n helper: synchronous import, not dynamic fetch
|
||||
|
||||
**Decision**: `i18n.ts` exports a function `getContent(locale: string)` that does a static import of each JSON and returns the correct one. No dynamic `fetch` or `fs.readFile`.
|
||||
|
||||
**Rationale**: Astro builds statically — all pages are rendered at build time. Static imports are tree-shakeable and type-safe. Dynamic fetch is unnecessary complexity.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **OG image missing** → Social card previews will have no image until a default OG image is created. Mitigation: `ogImage` prop defaults to empty string; the `<meta property="og:image">` tag is only rendered when `ogImage` is truthy.
|
||||
- **Stub Nav/Footer render nothing** → The smoke-test page will show Midnight body with no navigation. This is expected and clearly temporary. Mitigation: none needed — step 003 immediately follows.
|
||||
- **hreflang on smoke-test page** → `index.astro` (the brand smoke-test) will need placeholder `canonicalUrl` and `alternateUrl` props. Use `"/"` and `"/nl"` as stand-ins. This is fine since the smoke-test page will be replaced when the real homepage is built.
|
||||
Reference in New Issue
Block a user