Home of the shorebird.dev site: Astro 7 + Tailwind v4
with a few React islands, deployed to Firebase Hosting (firebase.json, GCP
project shorebird-website) by .github/workflows/main.yaml on every push to
main. Pull requests get a preview URL commented on the PR. See CUTOVER.md
for the DNS and custom-domain steps still pending.
firebase.json caches /_astro/** and /fonts/** for a year as immutable.
Astro fingerprints everything under /_astro/, but the font files in
public/fonts/ are named by hand — if a face is ever re-exported, change its
filename (and the @font-face/preload in src/styles/global.css and
src/layouts/main.astro) rather than overwriting it in place. Everything else,
including HTML, uses Hosting's default max-age=3600.
npm install
npm run dev # http://localhost:4321
npm run build # static site in dist/
npm run preview # serve dist/
Before committing: npm run format, npm run cspell, and (after a build)
npm run check:links.
docker compose up --build
src/content/blog/*.md— blog posts. Frontmatter:title,description,author(key into theauthorsmap insrc/layouts/blog.astro),date,cover, plus the Webflow-era fieldsintro,readingTime,ogImage,seoTitle,seoDescription,highlight. Body images live insrc/assets/blog/<slug>/, covers insrc/assets/blog/covers/, share images inpublic/blog/og/(JPEG on purpose: social crawlers don't reliably read WebP/AVIF forog:image; everything shown on-site goes through Astro's image pipeline, which emits WebP/AVIF itself).src/content/success-stories/*.md— customer stories, withindustry,companySize,websiteandhighlightsfor the sidebar.src/data/{reviews,logos,team}.json— the homepage testimonials, the "Trusted by" logo strip and the About page team grid. Image paths are/src/assets/...strings resolved withimport.meta.glob.src/pages/{dpa,privacy,terms}— legal pages as Markdown;/privacy/raw.jsonand/terms/raw.jsonexpose the Markdown as JSON for the console (the slash-less/privacy/rawand/terms/raw301 there; a static host takes the content type from the extension, so the name has to carry the.json).
Scripts (Python 3, stdlib only):
scripts/import_webflow.py— one-time import of the Webflow CMS export intosrc/contentandsrc/data(downloads images, converts HTML to Markdown, keeps hand-written posts). The snapshot it reads is kept outside the repo at../webflow-migration/webflow-export/(override withWEBFLOW_EXPORT=). Re-running regenerates every body that still carries the<!-- Converted from the Webflow CMS export ... -->marker and rewritessrc/data/*.json, so hand edits made after the import are lost unless the marker is removed first. It is kept for the final delta pull before cutover (seeCUTOVER.md); after that it is history, not a tool.scripts/check_links.py— crawlsdist/**/*.htmland reports internal links, images, scripts andog:imageURLs that do not resolve to a file.npm run check:linksruns it againstdist/.
The page background is dark (:root). Sections opt into light with
data-theme="light" (see src/components/ui/section.astro), which redefines
the surface/text/border tokens for everything inside. The sticky navbar's theme
is fixed per page and matches the first section (light on blog pages via
initialTheme="light", dark everywhere else); it does not change as sections
scroll under it. There is no system prefers-color-scheme switching, matching
the Webflow site.