↗ Live Demo · Your email-only domains are naked, and you know it. Here's a pair of pants.
You bought a domain. Maybe you're not ready to build the site yet. Maybe it's just an email domain and always will be. Either way, you are ready to stop staring at a parking page that says "Future home of something quite cool" like it's 2003.
Lily Livered is a one-page logo site for domains that deserve better than nothing but aren't getting a full website today. Drop in your SVG, edit one config file, push to Cloudflare Pages. Done before your coffee gets cold.
What this repo demonstrates: a one-page site built like a product with a roadmap. Tagged semver releases (v1.0.0 through v2.0.4) with a written migration guide, Renovate keeping dependencies current, Biome as a single lint-and-format pass, and a Lighthouse workflow that runs three times per push and takes the median so the badge number holds still. Security headers and a Mozilla Observatory grade sit on the front page because the tradeoffs behind them are written down, not buried. Small surface, real shipping discipline.
What you get:
- Lighthouse 100 on Accessibility, Best Practices, SEO, and yes, Performance, even with the demo's absurd logo (a genuinely enormous one could still nudge it down).
- A fun resizable logo that looks good on everything from a phone to a projector. Playing with the three size settings counts as an interactive feature.
- Inline-rendered SVG logo so it's not casually right-click-downloadable. See About the logo and "protection" for what it does and doesn't do.
- Three tiers of site metrics and visitor tracking to keep tabs on that baseline traffic flow
robots.txtandllms.txtso you can tell the cool kids what's going on- A 404 page, because even a single-page site needs to handle disappointment gracefully
About that Performance score: The demo site ships with a deliberately absurd 72 KB SVG logo: a 16th-century woodcut skull with 30,000+ path coordinates. It's a stress test, not a template recommendation, and it still scores 100. The logo is inlined as a
data:URI background-image, which is exactly what Lighthouse clocks as the Largest Contentful Paint. A genuinely enormous logo could still cost you a point or two; the badge workflow runs Lighthouse three times per push and takes the median to keep the number steady.
git clone https://github.com/vinsonconsulting/lily-livered.git my-site
cd my-site
npm ciThen do two things:
- Drop your logo into
src/assets/logo.svg(it gets inlined into the page at build time, so there's no/logo.svgURL on the deployed site). - Edit
src/config.jswith your details (see below).robots.txtand the sitemap are generated from yoursiteUrlat build time. Also replacepublic/llms.txtwith your own content, and replacepublic/favicon.svgwith your own brand mark (or delete it and sethasFavicon: false).
Preview it:
npm run dev # → localhost:4321Deploy it:
git add . && git commit -m "My domain has pants now" && git pushsrc/config.js is where everything lives:
export const config = {
// ◈ Site Info
siteName: 'Acme Corporation',
siteDescription: 'Fine Products for Coyotes Since 1949',
siteUrl: 'https://acme.example.com',
// ◈ Analytics. Paste your IDs, or leave '' to disable.
googleAnalyticsId: '', // e.g., 'G-XXXXXXXXXX'
clarityProjectId: '', // e.g., 'abc123xyz'
cloudflareAnalyticsToken: '', // e.g., 'abcd1234...'
// ◈ Colors
backgroundColor: '#000000',
textColor: '#ffffff',
lightBackgroundColor: '#ffffff', // used when colorScheme is 'auto'
lightTextColor: '#000000', // used when colorScheme is 'auto'
colorScheme: 'dark', // 'dark' | 'light' | 'auto'
// ◈ Logo size. How much screen to fill.
logoSize: 'normal', // 'normal' | 'large' | 'massive'
// ◈ Fade-in animation
fadeIn: true,
fadeInDuration: 1.2, // seconds
// ◈ Social sharing image (set true if you've added public/og-image.png)
hasOgImage: false,
// ◈ Favicon (set true if you've added public/favicon.svg)
hasFavicon: false,
// ◈ Structured data (JSON-LD for search engines)
enableStructuredData: true,
organizationType: 'Organization', // 'Organization', 'LocalBusiness', 'Person'
};| Setting | What it does | When to use it |
|---|---|---|
'normal' |
Comfortable fit, breathing room | Most logos |
'large' |
Fills most of the screen | Bold wordmarks, wide logos |
'massive' |
Edge-to-edge domination | You want the logo and nothing else |
| Setting | What it does | When to use it |
|---|---|---|
'dark' |
Always dark background | Default. Most logos look best on dark. |
'light' |
Always light background | Light-colored logos, bright brands |
'auto' |
Follows system preference | You want to play nice with OS dark mode |
When set to 'auto', the site uses backgroundColor/textColor for dark mode and lightBackgroundColor/lightTextColor for light mode. Make sure your logo looks good on both backgrounds, or use an SVG with its own prefers-color-scheme media query.
Replace src/assets/logo.svg with yours. The logo lives under src/ (not public/) so Astro can inline it into the HTML at build time. No /logo.svg URL is served to visitors. See About the logo and "protection" for the reasoning.
A few ground rules:
- Convert text to outlines. No font dependency headaches.
- Center it in the viewBox. If it looks off-center on the page, it's off-center in the file.
- White or light colors. Dark background is the default.
- Run it through SVGO. Smaller file, faster page (and smaller inlined HTML).
If your logo looks weirdly positioned: open it in Illustrator/Figma/Inkscape, select all, center on artboard, re-export.
The hero logo is inlined directly into the page HTML as a data: URI behind a CSS background-image, imported with Astro's ?raw so the SVG is never emitted as a separate asset. It's not served as a file. (A CSS background-image is also a Largest-Contentful-Paint candidate, so Lighthouse can still score Performance; an inline <svg> of <path>s can't be, which is why this isn't a bare <svg>.) That intentionally breaks the easy download paths:
- Right-click → "Save Image As..." doesn't appear (no
<img>element, plus the context menu is suppressed) - Drag-to-desktop doesn't produce a draggable image (
draggable="false"+user-drag: none) https://yoursite.com/logo.svgreturns 404. The URL simply doesn't exist.
This is friction, not protection. A determined user can View Source, copy the inlined SVG data URI out of the page, or screenshot the rendered page. There's no way to stop that, and we don't pretend otherwise. The goal here is to deter casual saves (the average visitor won't bother), not to make the logo unreachable.
If you actively want the logo to be downloadable (e.g., for press/brand-asset purposes), the simplest path is to also drop a copy at public/logo.svg so it's served alongside the inlined version. You'll lose the friction but gain the download.
robots.txt is generated at build time by src/pages/robots.txt.js using siteUrl from config.js, so there's nothing to edit. Set siteUrl correctly and the file just works. Output looks like:
User-agent: *
Allow: /
Sitemap: https://yourdomain.com/sitemap-index.xml
The sitemap itself is auto-generated by @astrojs/sitemap, also from siteUrl. One config field, two correct files.
Edit public/llms.txt so AI systems know who you are. This follows the llmstxt.org spec. The template ships with a placeholder. Replace it with your actual company info, services, and contact details.
Three options. All optional. All run off the main thread via Partytown so they don't slow anything down.
| Service | What it tells you | Get it at |
|---|---|---|
| Google Analytics 4 | Everything. Too much, probably. | analytics.google.com |
| Microsoft Clarity | Session recordings, heatmaps, rage clicks | clarity.microsoft.com |
| Cloudflare Web Analytics | Privacy-friendly basics, ~1KB | CF Dashboard ↗ Web Analytics |
Paste the ID into config.js. Leave blank to disable. They stack. Use one, two, or all three.
About Partytown and your security score: Partytown is only loaded when at least one analytics ID is configured. If all three are blank, it's excluded entirely. This matters because Partytown injects an inline bootstrap script, which forces 'unsafe-inline' into your Content Security Policy's script-src directive. Mozilla Observatory penalizes that by 20 points, enough to drop your grade from A+ to B+.
Out of the box (no analytics), the template scores A+ on Observatory and keeps a tight CSP. When you add an analytics ID, you'll need to add 'unsafe-inline' back to script-src in public/_headers:
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.clarity.ms;
That's a deliberate tradeoff: Observatory drops to B+, but you get off-thread analytics with zero main-thread cost. Partytown's inline script is the bootstrap that makes that possible. There's no way around it until Astro's experimental hash-based CSP graduates and can generate per-build nonces.
Favicon. The template ships with a placeholder public/favicon.svg and hasFavicon: true in config.js. Replace the file with your own brand mark, or delete the file and set hasFavicon: false to ship without one. The <link rel="icon"> tag is only emitted when hasFavicon: true, so there's no broken icon request either way. SVG favicons scale perfectly and support dark mode via CSS prefers-color-scheme media queries embedded in the SVG itself. In v1 the favicon fell back to /logo.svg when no favicon was provided; v2 removed that fallback because the logo is now inlined and has no public URL.
Social sharing image. Create a 1200×630 public/og-image.png (your logo on your background color), then set hasOgImage: true in config.js. This is what shows up when someone shares your link. The image meta tags are only emitted when you opt in, so no broken image URLs ship out of the box.
- Push this repo to GitHub
- Cloudflare Dashboard ↗ Pages ↗ Create project
- Connect your repo
- Build settings:
- Build command:
npm run build - Output directory:
dist - Framework preset: Astro
- Build command:
- Add your custom domain
Every git push after that auto-deploys.
After your Pages project is live on its *.pages.dev URL, point your domain to it:
- In Cloudflare Dashboard: Pages ↗ your project ↗ Custom domains ↗ Set up a custom domain
- Enter your domain (e.g.,
brand.example.comorexample.com) - Cloudflare handles the DNS. If your domain is already on Cloudflare DNS (and it should be), the CNAME record is added automatically.
- SSL is automatic. Give it a few minutes for the certificate to provision.
If your domain uses a different DNS provider: You'll need to add a CNAME record manually:
| Type | Name | Target |
|---|---|---|
CNAME |
@ or subdomain |
your-project.pages.dev |
Some DNS providers don't support CNAME at the apex (@). In that case, either use a subdomain or transfer your DNS to Cloudflare (free plan works fine).
Email-only domains: If you're using this template specifically for a domain that only handles email, make sure your MX records are set up before you add the domain to Pages. Cloudflare Pages won't interfere with existing MX records, but it's good hygiene to confirm email still works after the DNS change.
src/
├── config.js ← Edit this. All your settings.
├── assets/
│ └── logo.svg ← Replace this with your logo. Inlined into HTML at build time.
├── lib/
│ └── colors.js ← Shared color-scheme helper. Leave alone.
└── pages/
├── index.astro ← The page. You probably don't need to touch this.
├── 404.astro ← 404 page. Also probably leave it alone.
└── robots.txt.js ← Generates robots.txt from config.siteUrl.
public/
├── favicon.svg ← Replace with your brand mark (or delete + set hasFavicon: false).
├── llms.txt ← Edit with your company info.
├── _headers ← Security headers for Cloudflare Pages.
└── og-image.png ← Optional. For social media previews.
biome.json ← Lint/format config.
renovate.json ← Automated dependency updates.
npm run dev # Dev server at localhost:4321
npm run build # Production build → dist/
npm run preview # Preview the production build locally
npm run check # Lint + format check via Biome
npm run check:fix # Auto-fix lint and format issues
npm run check:astro # Astro diagnostics (.astro template errors)
npm run format # Format check only (no lint)
npm run format:fix # Apply Biome formattingIf you forked this template before v2.0.0, a few things changed:
- Logo moves:
public/logo.svg→src/assets/logo.svg. The build will fail with an Astro import error until you move it. - Node 22.12+ required: v2 follows Astro 6, which dropped Node 18 and 20. Bump your Cloudflare Pages
NODE_VERSIONenv var to22if it isn't already. - Favicon doesn't fall back to the logo: in v1,
hasFavicon: falsemade the favicon point at/logo.svg. In v2 there's no/logo.svgURL, so the favicon<link>is only emitted whenhasFavicon: true. The template ships with a placeholderpublic/favicon.svgand the flag set totrue. Replace the file or flip the flag. public/robots.txtwas removed (earlier in v1.0.x → v2 sequence): it's now generated dynamically fromconfig.siteUrlviasrc/pages/robots.txt.js. If you customized the old static file, port your customizations into the endpoint.
- Astro 6. Static site generator, zero client JS by default.
- Partytown. Runs analytics scripts off the main thread (loaded only when analytics are configured).
- Cloudflare Pages. Edge deployment, free tier is generous.
- Biome. Linting and formatting in one Rust binary.
- Renovate. Automated dependency updates so your clone doesn't rot.
- Semantic HTML,
prefers-reduced-motionsupport, high-contrast defaults - Auto-generated sitemap via
@astrojs/sitemap - Open Graph, Twitter Cards, JSON-LD structured data, all auto-generated from your config
- Security headers: HSTS, CSP, CORP, COOP, X-Frame-Options, Referrer-Policy, Permissions-Policy
- Lighthouse 100 on Accessibility, Best Practices, and SEO. Performance scales with your logo.
"Isn't this over-engineered for a one-page site?"
Yes, gloriously so. That's the point. Your domain gets the same edge deployment, security headers, structured data, and analytics pipeline as a site with a hundred pages, except you configured it in sixty seconds. The over-engineering is a feature, not a bug. You're welcome.
"Why does the demo logo look like it was drawn by a 16th-century monk?"
Because it was (see credits below). We wanted to prove the template handles complex SVGs gracefully. Your clean, modern logo will load faster and score higher on Performance.
"A 72 KB SVG? For a template about simplicity?"
The template is simple. The demo logo is a stress test: a 16th-century woodcut with 30,000+ coordinates, SVGO-optimized from 236 KB down to 72 KB (32 KB on the wire with compression). If it can handle this and still score in the high 80s on Performance, your logo doesn't stand a chance of slowing it down.
"Does it support dark mode / light mode?"
Yes. Set colorScheme: 'auto' in config.js and provide colors for both modes. The default is 'dark' because most logo-only sites look better on a dark background, and changing the default would break existing deployments. If your logo works on both dark and light backgrounds, 'auto' is the polite choice. It respects the visitor's system preference.
"Why Cloudflare Pages specifically?"
Free tier is generous (unlimited bandwidth, 500 builds/month), deploys are fast, and the edge network is global. You could deploy this to Vercel or Netlify with minimal changes, but Cloudflare Pages is the default because it's the best deal for a site that costs nothing to run.
"The 404 page used to load a Google Font. What happened?"
We replaced it with a system font stack. The original used Bebas Neue from Google Fonts, which meant an external network request on an error page (the one page that should load instantly and independently). The current stack (Impact / Arial Black / system sans-serif with text-transform: uppercase) hits the same bold display aesthetic without any external dependency.
"Why three analytics services?"
Because they do different things and they're all free. GA4 is the kitchen sink. Clarity gives you session recordings and heatmaps. Cloudflare Web Analytics is privacy-friendly and weighs 1 KB. Pick one, pick all three, pick none. They're off the main thread via Partytown so they don't affect performance.
The demo site logo is adapted from a woodcut on page 181 of Claude Paradin's Devises heroïques (Lyon: Jean de Tournes & Guillaume Gazeau, 1557). The image is in the public domain.
Apache-2.0. Use it, fork it, ship it. See LICENSE and NOTICE.
