UI/UX + Funnel + Hausverstand pass after the services/geo/static-pages/admin/cookie-banner sprint.
Live: https://elfixmobile-next.deni-4b0.workers.dev Routes: 1000 URLs in sitemap (4 locales × 250).
| # | Issue | Where | Why P0 |
|---|---|---|---|
| P0-1 | /buchen + /preisrechner render no <h1> in SSR HTML. Both pages wrap a client component (BookingFlow, Calculator) in <Suspense>; the <h1> lives inside that client tree, so the static HTML Google crawls contains only <script type="application/ld+json"> and an empty <main>. |
src/components/book/BookingFlow.tsx:152, src/components/calc/Calculator.tsx:103, src/app/[locale]/buchen/page.tsx, src/app/[locale]/preisrechner/page.tsx |
These are the two highest-intent landing pages on the whole site (book + price). Without an H1 in the rendered HTML, Google can't extract the page topic. |
| P0-2 | /api/lead returns {ok:true, deduped:true} on duplicate detection but the user sees the regular success card. If the dedup hash matched against an earlier aborted attempt, the customer thinks the booking went through but no email was sent. |
src/app/api/lead/route.ts |
Lost lead + communication disaster ("I booked you a week ago, why didn't you call?"). |
| P0-3 | Customers get no confirmation email. Only the shop owner (elfixmobile@gmx.at) is on the to field. The booking flow's success card surfaces a /status/<id> URL but most users never bookmark it. |
src/app/api/lead/route.ts |
Standard expectation for any booking flow; absence is a trust signal. |
| P0-4 | /status/<uuid> is publicly readable. UUID v4 has 122 bits of entropy so it isn't brute-forceable, but anyone with the link (forwarded email, browser history, screenshot) sees customer name + email + address + repair details. robots: noindex doesn't protect against direct access. |
src/app/[locale]/status/[id]/page.tsx, src/app/api/lead/route.ts |
GDPR: personal data on a guessable URL is a privacy leak. Fix with an HMAC token bound to a Worker secret. |
| # | Issue | Where | Notes |
|---|---|---|---|
| P1-1 | Sticky mobile CTA bar overlaps the bottom of the footer. The footer's pb-* doesn't account for the 64px sticky bar + iOS safe-area, so the "Webdesign von GoldenWing" credit and a couple of legal-line characters sit behind the bar on mobile. |
src/components/Footer.tsx, interaction with src/components/StickyMobileCTA.tsx |
Increase footer bottom padding on mobile to ~96px + safe-area. |
| P1-2 | Mobile menu mobileExtras still link to anchors (/#reviews, /#faq). Those anchors don't exist on most pages now that /bewertungen and /faq are real routes. User taps "FAQ" in the menu from a brand page → lands on the home page at a non-existent anchor. |
src/components/Nav.tsx |
Replace with real routes: /bewertungen, /faq, plus optionally /ueber-uns. |
| P1-3 | CookieBanner renders at z-[60] which is above the sticky CTA (z-40). On first visit the banner sits on top of the call/WhatsApp/book bar, so the primary action is untappable until consent is given. |
src/components/CookieBanner.tsx, src/components/StickyMobileCTA.tsx |
Either move the banner higher on the viewport (top-pinned) or hide the sticky CTA while the banner is open. |
| P1-4 | The admin UI at /[locale]/admin/leads renders inside the public [locale]/layout.tsx, so it gets the customer-facing nav, footer, sticky CTA and cookie banner. "Reparatur jetzt buchen" CTA bar above the admin panel is a hausverstand fail. |
src/app/[locale]/admin/leads/page.tsx, src/app/[locale]/layout.tsx |
Either move admin to its own route group (e.g. /admin/* outside [locale]) or add a per-path chrome-stripper. |
| P1-5 | Brand-page CTA for unpriced brands (Xiaomi, Google, Huawei, OnePlus, Sony, Nokia) deep-links to /buchen?device=Xiaomi. The booking flow honours ?device= but still starts at the service-picker step. So the user picks "walk-in / pickup / send" first, then sees the pre-filled device — feels backwards. |
src/components/brand/BrandQuoteCta.tsx, src/components/book/BookingFlow.tsx |
Pass ?service=walkin as a sensible default when coming from a brand quote CTA so the user lands directly on the damage/contact steps. |
| P1-6 | The /admin/leads URL is undiscoverable for the shop owner. No nav link, not in any footer, not in any docs. |
(organisational) | Add to README onboarding section + bookmark recommendation. |
| # | Issue | Where |
|---|---|---|
| P2-1 | /bewertungen reviews have no Schema.org Review markup. Only AggregateRating exists in the LocalBusiness JSON-LD on every page. Star rich-result eligibility wasted. |
src/app/[locale]/bewertungen/page.tsx |
| P2-2 | Per-page FAQ blocks on brand, service, model pages have no FAQPage JSON-LD. The central /faq does. Long-tail rich-result loss. |
src/components/brand/BrandFAQ.tsx, src/components/service/ServiceBody.tsx, model FAQ block |
| P2-3 | Footer "Company" column doesn't link to /bewertungen or /ueber-uns. Users have to find them via deep search. |
src/components/Footer.tsx |
| P2-4 | DistrictView hardcodes the 6-service grid. Should iterate SERVICES.slice(0, 6) so a service-set change propagates. |
src/components/district/DistrictView.tsx |
| P2-5 | OG image is identical for all 1000 pages — hardcoded "EL FIX MOBILE / Wie neu. In 30 Minuten." Per-page dynamic OG (brand, model, district) would lift CTR on social shares. | src/app/opengraph-image.tsx |
| P2-6 | hreflang x-default points to /de/<path>. Convention is language-neutral root; minor SEO impact. |
src/lib/seo.ts |
| P2-7 | 1000 URLs in one flat sitemap.xml. Google handles up to 50k per file so this works, but a sitemap-index.xml splitting by content type (brands / models / services / static) is cleaner once the site grows further. |
src/app/sitemap.ts |
| P2-8 | "Alle Modelle ansehen" CTA on brand pages links to /preisrechner?brand=... instead of a dedicated /reparatur/<brand>/models index. The 27 hidden models per brand are reachable only via individual long-tail URLs from sitemap. |
src/components/brand/BrandModelsTable.tsx |
- No custom-domain route in
wrangler.jsonc. Production switch toelfixmobile.atstill pending. - Build emits "Failed to load dynamic font for ★" — Satori can't fetch the star glyph in the OG image. Cosmetic.
- No
error.tsx/loading.tsx/not-found.tsxper locale — defaults serve unbranded errors. - Resend sender is
onboarding@resend.dev; onceelfixmobile.atis verified in Resend, swap tonoreply@elfixmobile.atfor trust + deliverability. - Booking phone validation is
z.string().min(6)— accepts garbage. Tighten with an AT-format regex. - API
totalfield has no max — payload could in theory inject huge numbers into the email subject. Cap at e.g. 100_000.
- 1000-URL sitemap with correct hreflang × 4 locales.
- JSON-LD graph: Service + Product + BreadcrumbList referencing a single
#localbusinessnode. - Five-layer
/api/leadhardening: honeypot → rate-limit → dedup → persist → send. All four behavioural layers verified live. - Lead persists in KV before Resend send, so a Resend outage doesn't lose the lead.
- IP stored as sha256 hash, not raw.
- Status tracker for customer.
- Admin endpoint fails closed (HTTP 503) when secrets are missing, not open.
- Cookie consent in
localStorage, not a self-referential cookie. - Mobile nav: ESC + route-change auto-close, 44×44 touch target, glass background always-on so it's visible on light pages.