Compare commits
1 Commits
main
...
seo/crawla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
425e56c0ca |
23
index.html
23
index.html
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
|
||||
|
||||
<!-- ── Primary meta ─────────────────────────────────────────────── -->
|
||||
<title>Plan-B — AI-Assisted Crisis Preparedness Advisor</title>
|
||||
<meta name="description" content="Free AI-assisted crisis-preparedness advisor for urban households. Assess your readiness across 9 collapse scenarios and get a personalised survival plan in minutes."/>
|
||||
<title>Plan-B — AI-Assisted Preparedness for Urban Households</title>
|
||||
<meta name="description" content="A calm, costed preparedness plan for your household in about 10 minutes. Check your readiness across 6 areas like water, energy and food, built on economics not fear."/>
|
||||
<meta name="author" content="Plan-B"/>
|
||||
<meta name="theme-color" content="#5A9A78"/>
|
||||
<meta name="color-scheme" content="light"/>
|
||||
@@ -16,8 +16,8 @@
|
||||
<!-- ── Open Graph (Facebook, LinkedIn, WhatsApp, Signal, Telegram…) ─ -->
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:site_name" content="Plan-B"/>
|
||||
<meta property="og:title" content="Plan-B — AI-Assisted Crisis Preparedness Advisor"/>
|
||||
<meta property="og:description" content="Assess your household's readiness across 9 collapse scenarios and get a personalised survival plan — water, food, energy, medical and budget. Built for city apartments."/>
|
||||
<meta property="og:title" content="Plan-B — AI-Assisted Preparedness for Urban Households"/>
|
||||
<meta property="og:description" content="Find out where your household is exposed and get a clear, costed 90-day plan. Water, energy, food and more, made for city apartments."/>
|
||||
<meta property="og:url" content="https://plan-b.now/"/>
|
||||
<meta property="og:image" content="https://plan-b.now/images/og-image.png"/>
|
||||
<meta property="og:image:secure_url" content="https://plan-b.now/images/og-image.png"/>
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
<!-- ── Twitter / X ─────────────────────────────────────────────── -->
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:title" content="Plan-B — AI-Assisted Crisis Preparedness Advisor"/>
|
||||
<meta name="twitter:description" content="Assess your household's readiness across 9 collapse scenarios and get a personalised survival plan. Built for city apartments."/>
|
||||
<meta name="twitter:title" content="Plan-B — AI-Assisted Preparedness for Urban Households"/>
|
||||
<meta name="twitter:description" content="A calm, costed 90-day preparedness plan for your household. Made for city apartments."/>
|
||||
<meta name="twitter:image" content="https://plan-b.now/images/og-image.png"/>
|
||||
<meta name="twitter:image:alt" content="Plan-B — AI-Assisted Preparedness for Urban Households"/>
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png"/>
|
||||
<link rel="manifest" href="/site.webmanifest"/>
|
||||
|
||||
<!-- ── Fonts ───────────────────────────────────────────────────── -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Serif+Display&family=Barlow:wght@300;400;500;600&display=swap" rel="stylesheet"/>
|
||||
<!-- ── Fonts (self-hosted — no third-party requests) ───────────── -->
|
||||
<link rel="preload" href="/fonts/barlow-400-latin.woff2" as="font" type="font/woff2" crossorigin/>
|
||||
<link rel="preload" href="/fonts/dm-serif-display-400-latin.woff2" as="font" type="font/woff2" crossorigin/>
|
||||
<link rel="preload" href="/fonts/space-mono-400-latin.woff2" as="font" type="font/woff2" crossorigin/>
|
||||
<link rel="stylesheet" href="/fonts/fonts.css"/>
|
||||
|
||||
<!-- ── Structured data (Google rich results) ───────────────────── -->
|
||||
<script type="application/ld+json">
|
||||
@@ -57,7 +58,7 @@
|
||||
"@id": "https://plan-b.now/#website",
|
||||
"url": "https://plan-b.now/",
|
||||
"name": "Plan-B",
|
||||
"description": "AI-assisted crisis-preparedness advisor for urban households.",
|
||||
"description": "AI-assisted preparedness for urban households.",
|
||||
"inLanguage": ["en", "de"],
|
||||
"publisher": { "@id": "https://plan-b.now/#org" }
|
||||
},
|
||||
|
||||
21
nginx.conf
21
nginx.conf
@@ -24,10 +24,25 @@ server {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# SPA fallback — any non-asset path serves index.html so future
|
||||
# client-side routing works without 404s.
|
||||
# App shell — only the root path serves index.html. The app is a
|
||||
# single-route SPA (no history-based client routing), so there is no
|
||||
# need to fall back unknown paths to index.html.
|
||||
location = / {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
# Everything else serves a real file if it exists, otherwise a true 404.
|
||||
# This avoids the soft-404 (HTTP 200 on unknown paths) that hurts SEO.
|
||||
# When real redirects/removals appear, add `return 301`/`return 410`
|
||||
# location blocks above this one.
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Branded 404 page (shipped from public/404.html).
|
||||
error_page 404 /404.html;
|
||||
location = /404.html {
|
||||
internal;
|
||||
}
|
||||
|
||||
# Don't serve dotfiles
|
||||
|
||||
35
public/404.html
Normal file
35
public/404.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="robots" content="noindex, follow"/>
|
||||
<title>Page not found — Plan-B</title>
|
||||
<link rel="icon" href="/images/favicon.svg" type="image/svg+xml"/>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
html, body { height: 100%; margin: 0; }
|
||||
body {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: #FAFAFA; color: #2a3010;
|
||||
font-family: "Barlow", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
text-align: center; padding: 2rem;
|
||||
}
|
||||
.wrap { max-width: 32rem; }
|
||||
h1 { font-size: 3.5rem; margin: 0 0 .5rem; font-weight: 600; letter-spacing: -0.02em; }
|
||||
p { font-size: 1.125rem; line-height: 1.6; margin: 0 0 1.75rem; color: #4a5340; }
|
||||
a.home {
|
||||
display: inline-block; padding: .75rem 1.5rem; border-radius: .5rem;
|
||||
background: #5A9A78; color: #fff; text-decoration: none; font-weight: 500;
|
||||
}
|
||||
a.home:hover { background: #4d8567; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>404</h1>
|
||||
<p>This page isn’t here. The plan still is — head back to the start and check your household readiness.</p>
|
||||
<a class="home" href="/">Back to Plan-B</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/fonts/barlow-300-latin-ext.woff2
Normal file
BIN
public/fonts/barlow-300-latin-ext.woff2
Normal file
Binary file not shown.
BIN
public/fonts/barlow-300-latin.woff2
Normal file
BIN
public/fonts/barlow-300-latin.woff2
Normal file
Binary file not shown.
BIN
public/fonts/barlow-400-latin-ext.woff2
Normal file
BIN
public/fonts/barlow-400-latin-ext.woff2
Normal file
Binary file not shown.
BIN
public/fonts/barlow-400-latin.woff2
Normal file
BIN
public/fonts/barlow-400-latin.woff2
Normal file
Binary file not shown.
BIN
public/fonts/barlow-500-latin-ext.woff2
Normal file
BIN
public/fonts/barlow-500-latin-ext.woff2
Normal file
Binary file not shown.
BIN
public/fonts/barlow-500-latin.woff2
Normal file
BIN
public/fonts/barlow-500-latin.woff2
Normal file
Binary file not shown.
BIN
public/fonts/barlow-600-latin-ext.woff2
Normal file
BIN
public/fonts/barlow-600-latin-ext.woff2
Normal file
Binary file not shown.
BIN
public/fonts/barlow-600-latin.woff2
Normal file
BIN
public/fonts/barlow-600-latin.woff2
Normal file
Binary file not shown.
BIN
public/fonts/dm-serif-display-400-latin-ext.woff2
Normal file
BIN
public/fonts/dm-serif-display-400-latin-ext.woff2
Normal file
Binary file not shown.
BIN
public/fonts/dm-serif-display-400-latin.woff2
Normal file
BIN
public/fonts/dm-serif-display-400-latin.woff2
Normal file
Binary file not shown.
115
public/fonts/fonts.css
Normal file
115
public/fonts/fonts.css
Normal file
@@ -0,0 +1,115 @@
|
||||
/* Self-hosted fonts — replaces fonts.googleapis.com.
|
||||
Subsets: latin + latin-ext. font-display: swap.
|
||||
Source: Google Fonts (SIL OFL 1.1). Regenerate via the fonts download step. */
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-300-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-300-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-400-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-400-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-500-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-500-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-600-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Barlow';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/fonts/barlow-600-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'DM Serif Display';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/dm-serif-display-400-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'DM Serif Display';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/dm-serif-display-400-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/space-mono-400-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/space-mono-400-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/space-mono-700-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/space-mono-700-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
BIN
public/fonts/space-mono-400-latin-ext.woff2
Normal file
BIN
public/fonts/space-mono-400-latin-ext.woff2
Normal file
Binary file not shown.
BIN
public/fonts/space-mono-400-latin.woff2
Normal file
BIN
public/fonts/space-mono-400-latin.woff2
Normal file
Binary file not shown.
BIN
public/fonts/space-mono-700-latin-ext.woff2
Normal file
BIN
public/fonts/space-mono-700-latin-ext.woff2
Normal file
Binary file not shown.
BIN
public/fonts/space-mono-700-latin.woff2
Normal file
BIN
public/fonts/space-mono-700-latin.woff2
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Plan-B — Crisis Preparedness Advisor",
|
||||
"name": "Plan-B — Preparedness for Urban Households",
|
||||
"short_name": "Plan-B",
|
||||
"description": "AI-assisted crisis-preparedness advisor for urban households.",
|
||||
"description": "AI-assisted preparedness for urban households.",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://plan-b.now/</loc>
|
||||
<lastmod>2026-06-18</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://plan-b.now/"/>
|
||||
<xhtml:link rel="alternate" hreflang="de" href="https://plan-b.now/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://plan-b.now/"/>
|
||||
</url>
|
||||
</urlset>
|
||||
|
||||
@@ -589,8 +589,8 @@ import { onMounted } from 'vue'
|
||||
const T = {
|
||||
en: {
|
||||
brand: "Plan-B",
|
||||
page_title: "Plan-B — AI-Assisted Crisis Preparedness Advisor",
|
||||
meta_description: "Free AI-assisted crisis-preparedness advisor for urban households. Assess your readiness across 9 collapse scenarios and get a personalised survival plan in minutes.",
|
||||
page_title: "Plan-B — AI-Assisted Preparedness for Urban Households",
|
||||
meta_description: "A calm, costed preparedness plan for your household in about 10 minutes. Check your readiness across 6 areas like water, energy and food, built on economics not fear.",
|
||||
og_locale: "en_US",
|
||||
hero_eyebrow: "Crisis Preparedness Advisor",
|
||||
hero_sub: "AI-Assisted Preparedness for Urban Households",
|
||||
@@ -713,8 +713,8 @@ const T = {
|
||||
},
|
||||
de: {
|
||||
brand: "Plan-B",
|
||||
page_title: "Plan-B — KI-gestützter Krisenvorsorge-Berater",
|
||||
meta_description: "Kostenloser KI-gestützter Krisenvorsorge-Berater für urbane Haushalte. Bewerte deine Vorsorge über 9 Krisenszenarien und erhalte in Minuten einen personalisierten Survival-Plan.",
|
||||
page_title: "Plan-B — KI-gestützte Vorsorge für urbane Haushalte",
|
||||
meta_description: "Ein besonnener, kalkulierter Vorsorgeplan für Ihren Haushalt in rund 10 Minuten. Prüfen Sie Ihre Vorsorge in 6 Bereichen wie Wasser, Energie und Lebensmitteln, ökonomisch fundiert statt angstgetrieben.",
|
||||
og_locale: "de_DE",
|
||||
hero_eyebrow: "⚡ Krisenvorsorge-Berater",
|
||||
hero_sub: "KI-gestützte Vorsorge für urbane Haushalte",
|
||||
|
||||
45
src/seo-snapshot.html
Normal file
45
src/seo-snapshot.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
Static SEO snapshot — injected into a <noscript> in the served HTML by
|
||||
vite-plugin-seo-snapshot.js. Browsers with JavaScript enabled IGNORE this
|
||||
block entirely, so it has ZERO effect on the live app/UX. It exists so that
|
||||
`curl` and non-rendering crawlers receive real <h1> + content + <a href>
|
||||
links instead of an empty shell. Keep copy on-brand (preparedness, not
|
||||
crisis/survival) and factually aligned with the app.
|
||||
-->
|
||||
<main class="seo-snapshot" role="main">
|
||||
<h1>Plan-B — AI-Assisted Preparedness for Urban Households</h1>
|
||||
|
||||
<p>Plan-B builds a calm, costed preparedness plan for your household in about
|
||||
ten minutes. It checks where your home is exposed and gives you a clear,
|
||||
prioritised 90-day plan — built on economics, not fear. Made for city
|
||||
apartments.</p>
|
||||
|
||||
<h2>What Plan-B checks</h2>
|
||||
<p>Answer a short set of questions about your household and get a readiness
|
||||
picture across the core areas that keep a home running through a power outage,
|
||||
a supply disruption, or a service interruption:</p>
|
||||
<ul>
|
||||
<li><strong>Water</strong> — storage, filtration and backup supply.</li>
|
||||
<li><strong>Food</strong> — reserves, shelf-stable staples and rotation.</li>
|
||||
<li><strong>Growing</strong> — fresh food you can produce at home.</li>
|
||||
<li><strong>Energy</strong> — power and heat backup for an apartment.</li>
|
||||
<li><strong>Medical</strong> — first aid and essential supplies.</li>
|
||||
<li><strong>Sanitation</strong> — hygiene and waste when services pause.</li>
|
||||
</ul>
|
||||
|
||||
<h2>How it works</h2>
|
||||
<ol>
|
||||
<li>Answer a few questions about your household and location.</li>
|
||||
<li>See where you are exposed across each readiness area.</li>
|
||||
<li>Get a costed, prioritised 90-day plan you can act on.</li>
|
||||
</ol>
|
||||
|
||||
<nav aria-label="Site">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/#about-section">About Plan-B</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<p>Plan-B is available in English and German (Kammergut).</p>
|
||||
</main>
|
||||
25
vite-plugin-seo-snapshot.js
Normal file
25
vite-plugin-seo-snapshot.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
// Injects a static, crawlable SEO snapshot into a <noscript> block in the
|
||||
// served index.html. Browsers with JS enabled ignore <noscript>, so the live
|
||||
// app and its UX are completely unaffected. Non-rendering crawlers and `curl`
|
||||
// receive a real <h1>, descriptive content and <a href> links instead of an
|
||||
// empty SPA shell.
|
||||
export default function seoSnapshot() {
|
||||
const snapshotPath = fileURLToPath(new URL('./src/seo-snapshot.html', import.meta.url))
|
||||
return {
|
||||
name: 'seo-snapshot',
|
||||
transformIndexHtml(html) {
|
||||
const snapshot = readFileSync(snapshotPath, 'utf8')
|
||||
.replace(/<!--[\s\S]*?-->/g, '') // strip authoring comments from served HTML
|
||||
.trim()
|
||||
const block = `<noscript>\n${snapshot}\n</noscript>`
|
||||
// Place it immediately after the app mount point.
|
||||
return html.replace(
|
||||
'<div id="app"></div>',
|
||||
`<div id="app"></div>\n${block}`,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import seoSnapshot from './vite-plugin-seo-snapshot.js'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
plugins: [vue(), seoSnapshot()],
|
||||
server: { port: 5173 }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user