Skip to content

Latest commit

 

History

History
199 lines (153 loc) · 8.72 KB

DEVELOPING.md

File metadata and controls

199 lines (153 loc) · 8.72 KB

Developing this site

Developer documentation for the RelayStack site. The sales copy lives in README.md — this file covers how the thing is built, deployed and kept consistent.

Stack

  • Astro 7, static output. One runtime dependency; two dev dependencies.
  • Zero client-side JavaScript. The numbers below are from the built output, not an aspiration.
  • Self-hosted variable fonts (Archivo, Archivo Narrow). No fonts.googleapis.com, so there is no third-party request and therefore no cookie banner.
  • Native endpoints for sitemap.xml, robots.txt and llms.txt — no @astrojs/sitemap, so the URL list is explicit and reviewable.
22 pages built   0 JS files   11 KB stylesheet   424K total
astro check      0 errors, 0 warnings, 0 hints

Running it

npm install
npm run dev        # http://localhost:4321
npm run build      # -> dist/
npm run preview    # serve the built output
npm run check      # astro check: types, content schemas, a11y hints
npm run verify     # check + README price sync + build + link check — the gates CI runs
npm run study      # measure a domain list; see below

PRODUCT.md and DESIGN.md sit at the project root so the design tooling can find them, and are gitignored — they record product truth and the visual system, and are deliberately not published.

Editing the business

src/config.ts is the one file that matters. Business name, the Telegram contact, the service area, the published prices, and the market research the prices were placed against. The footer, the schema, the price page and llms.txt all read from it, so they cannot contradict each other.

The README's price table is generated from it. README.md is Markdown and cannot import the config, so the table between the <!-- prices:start --> and <!-- prices:end --> markers is spliced in by scripts/sync-readme.mjs:

npm run sync:readme            # rewrite the table from src/config.ts
npm run sync:readme -- --check # fail if it has drifted (the deploy runs this)

Change a price in src/config.ts and run npm run sync:readme. If you forget, the deploy stops rather than publishing a page and a README that disagree.

Prices are the owner's own numbers, sitting against recorded market figures rather than invented ones. The euro price is converted at a stated rate — if you change it, change the rate note too.

Adding a service

Drop a Markdown file in src/content/leistungen/. The filename becomes the URL.

---
seoTitle: "Website erstellen lassen – Festpreis, wartungsfrei"   # <title>
h1: "Website erstellen lassen"                                    # on-page heading
title: Website                                                    # short name for nav and lists
description: 50–220 characters. Becomes the meta description and the llms.txt line.
summary: One sentence for listings.
order: 1
includes:
  - "A concrete, checkable inclusion"
faqs:
  - q: A question a customer actually asks
    a: A short, self-contained answer.
---

Body copy. The FAQ frontmatter is emitted as FAQPage schema **and** rendered as a
`<details>` block from the same source, so the two can never disagree.

Then add the matching entry to prices in src/config.ts, and add the route to src/pages/sitemap.xml.ts (the collection pages are picked up automatically; the static list is not).

Adding an article

Drop a Markdown file in src/content/wissen/. Same pattern, no includes. Extra frontmatter keys are allowed and ignored. The h1 is rendered by the layout, so start the body with the answer — the first paragraph is the part assistants quote.

How the SEO files are generated

All three read the same content collections the pages do:

File Source
sitemap.xml static route list + both collections, with lastmod
robots.txt AI crawlers allowed on purpose — blocking them removes you from AI answers
llms.txt services, prices, articles and contact details

/impressum/ is excluded from the sitemap and set to noindex until its mandatory details are filled in. Nothing else is.

Deploying

Push to main. .github/workflows/deploy.yml builds, verifies that sitemap.xml, robots.txt and llms.txt exist and are non-empty, and deploys to GitHub Pages. A build that ships without them fails rather than publishing.

One-time setup, already done for this repo: Settings → Pages → Source = GitHub Actions. It must not be left on "Deploy from a branch" — that mode serves files straight from the branch without running a build, which is why this repo's Pages previously sat in an errored state with a 404.

The share card

public/og-card.png is committed, but it is generated — /og-card/ is a real page rendered by the site's own stylesheet and typefaces, then captured at exactly 1200×630. Regenerate it after any change to the headline or the palette:

npm run preview            # in one terminal
npm i --no-save puppeteer  # in another
node scripts/og-card.mjs

Puppeteer is deliberately not a dependency: the deploy runs npm ci and should not download a browser.

Design rules

The site is built in one world — the embossed label tape of a German workshop — and these rules are what hold it together. Breaking one is a decision, not an edit.

  • The tape strip is the only container. Headings, section labels and actions are tape. There are no cards, no pills and no rounded panels anywhere.
  • One orange strip per screen, and it is always the action. The accent is functional.
  • Two faces, both self-hosted. Tape and display type is Archivo Narrow; text is Archivo.
  • Depth is the tape's own lift and emboss. Nothing else casts a shadow.
  • No JavaScript. Interaction is <details>, :target and scroll-driven CSS, all of which degrade to something that still works.

Content rules

These are correctness rules, not preferences. The site's credibility rests on them.

  • No location is published. No city, no canton, no region. The business is explicitly remote.
  • No invented proof. No testimonials, no client names, no percentages, no guarantees, no years-of-experience claims. Client work is described by sector only.
  • Illustrative values are labelled as such. Any example measurement on the site carries a visible note saying it is not real client data.
  • Numbers cite their source. Market figures come with an attribution or they do not ship.
  • The Impressum must be completed before commercial use. A DACH Impressum requires a name and an address; the page is a marked draft until those exist.

Structure

src/
  config.ts              business facts, prices, market research  ← edit this first
  content.config.ts      collection schemas
  content/
    leistungen/          one Markdown file per service
    wissen/              articles
  lib/schema.ts          Organization, Service, FAQPage, BreadcrumbList
  components/            Footer, JsonLd, IconTelegram, Mechanism
  layouts/Base.astro     head, meta, nav, canonical, hreflang
  pages/                 routes; sitemap.xml.ts, robots.txt.ts, llms.txt.ts, og-card.astro
  styles/                fonts.css (self-hosted faces), global.css (the design system)
public/
  fonts/                 variable woff2
  og-card.png            generated; see above
scripts/
  og-card.mjs            regenerates the share card (needs puppeteer, installed ad hoc)
  sync-readme.mjs        splices the price table into README.md from src/config.ts
  check-links.mjs        fails on any internal link whose target does not exist
  visibility-study.mjs   measures a list of sites; see "The visibility study" below

The visibility study

scripts/visibility-study.mjs measures, for a list of real business websites, whether a search engine or an assistant can read and quote them: HTTPS, viewport, meta description, canonical, structured data (including LocalBusiness and FAQPage), sitemap, llms.txt, whether robots.txt blocks AI crawlers, and page weight.

npm run study data/domains.txt --out data/study-2026.json

The input is one domain per line, optionally domain,country,sector. It runs sequentially, rate-limits itself, identifies itself in the user agent, and reads only public pages.

Two limits it will not hide, and a published study must state both:

  1. It does not measure AI mentions. That needs model API keys and a fixed prompt set. The generated summary says so explicitly, so the numbers cannot be reported as AI visibility.
  2. It reads server-rendered HTML only. Sites that inject JSON-LD with JavaScript are counted as having none. It understates and never overstates.

Nothing here is a substitute for the sample: the domain list has to be a real, defined population, and the study is only as honest as that list.