Skip to content

Latest commit

 

History

History
61 lines (49 loc) · 7.68 KB

File metadata and controls

61 lines (49 loc) · 7.68 KB

L99 Senior Audit — 2026-05-14

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).

P0 — funnel/SEO blockers, fix immediately

# 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.

P1 — UX frustration, fix this week

# 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.

P2 — Polish, next iteration

# 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

P3 — Code hygiene / future

  • No custom-domain route in wrangler.jsonc. Production switch to elfixmobile.at still 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.tsx per locale — defaults serve unbranded errors.
  • Resend sender is onboarding@resend.dev; once elfixmobile.at is verified in Resend, swap to noreply@elfixmobile.at for trust + deliverability.
  • Booking phone validation is z.string().min(6) — accepts garbage. Tighten with an AT-format regex.
  • API total field has no max — payload could in theory inject huge numbers into the email subject. Cap at e.g. 100_000.

✅ What's solid

  • 1000-URL sitemap with correct hreflang × 4 locales.
  • JSON-LD graph: Service + Product + BreadcrumbList referencing a single #localbusiness node.
  • Five-layer /api/lead hardening: 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.