Codebase Audit Report

Full-spectrum diagnosis · 103 files · 24,059 lines of TypeScript
7 March 2026 · Next.js 16 + Supabase + Tailwind v4
Executive Summary
4
Critical Issues
5
High Issues
6
Medium Issues
5
Low Issues
13
Strengths
0%
Test Coverage
MetricValueStatus
TypeScript Build0 errorsClean
ESLint13 errors, 18 warningsNeeds Fix
Test Coverage0% — no test filesMissing
Pages / Routes35 pagesComprehensive
API Endpoints13 routesNo Rate Limits
Tools13 interactive toolsWell-built
Client Components30 of 103 filesWell-split
Security HeadersNone configuredMissing
Error Boundaries0 error.tsx / loading.tsxMissing
SEO MetadataAll 35 pages coveredComplete
Critical Issues 4 Found
vpnmarkt-ts7 Race Condition in Review Vote Counting Critical

The review vote endpoint uses a read-then-update pattern for incrementing helpful_count / not_helpful_count. Two concurrent voters can read the same count value and write back the same incremented value, causing one vote to be silently lost.

src/app/api/review-vote/route.ts:39-57
Recommended Fix Use an atomic SQL increment via Supabase RPC: UPDATE reviews SET helpful_count = helpful_count + 1 WHERE id = $1, or create a Postgres function and call it with supabase.rpc('increment_vote', ...).
vpnmarkt-efe No Rate Limiting on Any API Route Critical

All 13 API endpoints accept unlimited requests. The breach-check endpoint proxies to an external API — abuse could get VPNMarkt's server IP blocked. The newsletter subscribe endpoint has no CAPTCHA and could be abused by spam bots. The review submit and track endpoints allow unlimited writes to the database.

Affected: /api/review/submit, /api/breach-check, /api/track, /api/newsletter/subscribe, /api/review-vote, /api/censorship-check, /api/price-alert, /api/location-search, /api/exchange-rates
Recommended Fix Add Next.js middleware with IP-based rate limiting (e.g., @vercel/functions rate limiter, or Upstash Redis). Apply stricter limits to write endpoints (5 req/min) and external API proxies (10 req/min).
vpnmarkt-ia7 No Security Headers Configured Critical

The site is missing all standard security headers: Content-Security-Policy, X-Frame-Options, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy. Only poweredByHeader: false is set.

next.config.ts:3-12
Recommended Fix Add a headers() async function in next.config.ts returning an array of security headers for all routes. At minimum: HSTS, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin.
vpnmarkt-reo Zero Error Boundaries, Loading States, or Suspense Critical

The entire app has zero error.tsx files, zero loading.tsx files, and zero Suspense boundaries. If any server component throws (e.g., Supabase is down), users see Next.js's default blank error page with no recovery path. Dynamic pages with data fetching show no loading skeleton during navigation.

Entire src/app/ directory — 35 routes with no error handling
Recommended Fix Add a root-level src/app/error.tsx (client component) and src/app/loading.tsx as minimum safety nets. Add route-group-specific versions for critical paths like /vpn/[slug] and /compare.

High Priority 5 Found
vpnmarkt-flw 13 ESLint Errors Including Component-During-Render Bug High

TestResultsDashboard.tsx:100 defines SortIcon as a component inside the render function. React will destroy and recreate this on every render, losing internal state. Additionally, CurrencyProvider.tsx:52 and DealOfTheDay.tsx:15 call setState synchronously inside useEffect, triggering cascading re-renders. There are also 7 unused variable warnings.

src/app/methodik/TestResultsDashboard.tsx:100 · src/components/currency/CurrencyProvider.tsx:52 · src/components/ui/DealOfTheDay.tsx:15
vpnmarkt-1s4 Homepage Metadata in English on a German Site High

The HTML tag is lang="de" but the homepage title, description, OpenGraph, and Twitter metadata are all in English: "Compare, Review & Find VPN Deals". Every other page in the site has proper German metadata. The root layout metadata.title.default is also English, affecting any page that doesn't override it.

src/app/layout.tsx:31-35 · src/app/page.tsx:10-26
vpnmarkt-7uu Missing Revalidation on Dynamic Pages High

Three data-fetching pages lack export const revalidate, making them fully dynamic (SSR on every request): the homepage (page.tsx), the coupons page (coupons/page.tsx), and the quiz (quiz/page.tsx). The homepage is the most-visited page and fetches VPN data from Supabase on every single request.

src/app/page.tsx · src/app/coupons/page.tsx · src/app/quiz/page.tsx
vpnmarkt-396 Zero Test Infrastructure High

No test files, no test runner configuration (jest, vitest), no testing dependencies in package.json. 13 API routes with business logic (vote counting, newsletter flow, click tracking, review submission) are completely untested. Quiz scoring, currency conversion, and comparison helpers have zero coverage.

vpnmarkt-8we Supabase Query Errors Silently Swallowed High

Most page-level Supabase queries destructure only { data }, ignoring the error field. If Supabase is unreachable or returns an error, pages silently render with empty data arrays — showing users a blank page with no VPNs, coupons, or reviews, and no indication anything is wrong.

src/app/page.tsx:32 · src/app/compare/page.tsx:63 · src/app/coupons/page.tsx:28 · src/app/vpn/[slug]/page.tsx:69

Medium Priority 6 Found
vpnmarkt-7pe 543 Hardcoded Color Values in Style Props Medium

Design system colors are defined as CSS variables in globals.css but used as string literals in inline style={{}} props throughout the codebase. #171008 appears 100+ times, #f5f0e8 80+ times, #c0311e 60+ times. This makes theme changes extremely painful and error-prone.

vpnmarkt-oci God Components Exceeding 900 Lines Medium

Several components handle too many responsibilities: CompareExperience.tsx at 1,319 lines (filtering, URL state, rendering), beste-vpn/[slug]/page.tsx at 931 lines, IpChecker.tsx at 835 lines, vpn/[slug]/page.tsx at 678 lines. These should be broken into focused sub-components.

vpnmarkt-234 No Code Splitting for Heavy Client Components Medium

No next/dynamic imports anywhere in the codebase. Heavy client components like SpeedChart, QuizWizard (664 lines), and CompareExperience (1,319 lines) are loaded synchronously, increasing initial bundle size for pages that embed them.

vpnmarkt-8fz Newsletter Auto-Confirms Without Email Verification Medium

The newsletter subscribe endpoint generates a confirmation token but then immediately auto-confirms the subscription without sending a verification email. A comment on line 68 says "In production, send confirmation email here". Double opt-in is required by German law (GDPR/UWG §7).

src/app/api/newsletter/subscribe/route.ts:68-73
vpnmarkt-shp Duplicated Click Tracking Aggregation Logic Medium

Identical todayCountMap and allCountMap aggregation code is copy-pasted between the VPN detail page and the coupons page. Both loop over click events to build frequency maps using the exact same pattern.

src/app/vpn/[slug]/page.tsx:88-130 · src/app/coupons/page.tsx:36-73
vpnmarkt-6z9 274 Inline Style Objects Instead of CSS Classes Medium

Complex conditional style objects are used extensively instead of Tailwind classes or CSS modules. The Navbar alone has 15+ inline style objects with hardcoded colors. This bypasses Tailwind's utility system, makes components verbose, and prevents effective style reuse.

src/components/layout/Navbar.tsx (15+ inline styles) · Throughout all page.tsx files

Low Priority 5 Found
vpnmarkt-o5o Font Loading Could Be Optimized Low

Three Google Fonts are loaded (Playfair Display with 6 weights, Lora with 4 weights, IBM Plex Mono with 4 weights). No explicit font-display: swap is set. While Next.js handles some optimization automatically, explicit preloading for the critical Playfair Display font would reduce FOUT risk.

src/app/layout.tsx:8-26
vpnmarkt-c4g Missing generateStaticParams for Known Slugs Low

Dynamic routes like /vpn/[slug], /glossar/[slug], and /ratgeber/[slug] don't export generateStaticParams. Pre-generating pages for known slugs would enable static generation with ISR, reducing TTFB for first visitors.

vpnmarkt-h6k Unused CSS Class Low

.animate-glow-pulse is defined in globals.css:381 with a comment "no-op in editorial theme" but is never referenced anywhere in the codebase.

vpnmarkt-bgj No Favicon Variants or PWA Manifest Low

Only favicon.ico exists. Missing apple-touch-icon.png, icon-192.png, icon-512.png, and manifest.json for progressive web app support and proper bookmark icons on mobile devices.

vpnmarkt-jt6 Sitemap Creates Its Own Supabase Client Low

The sitemap generator at sitemap.ts:60-63 creates its own createClient() instance directly from @supabase/supabase-js instead of using the shared @/lib/supabase/server helper. This bypasses any middleware or cookie handling that the shared helper provides.


Architecture Strengths 13 Found
Zero any types in the entire 24K-line codebase. All types are explicit or properly inferred.
Clean server/client split. Only 30 of 103 files use 'use client', all justified by hooks or browser APIs.
Comprehensive SEO metadata on all 35 pages with OpenGraph, Twitter cards, and canonical URLs.
Rich structured data (JSON-LD) on all tool pages, VPN detail pages, FAQ pages, and the organization entity.
Proper RLS policies on all Supabase tables. Reviews require is_approved: true to read; coupons require is_active: true.
Cron job authentication with CRON_SECRET Bearer token validation on both scheduled endpoints.
Parallel data fetching with Promise.all() across all pages. No N+1 query patterns found.
Proper sitemap with dynamic entries, lastModified dates, and a separate news sitemap for /nachrichten.
Good accessibility: aria-label, aria-expanded, aria-current on navbar. All images have meaningful alt text.
All images use next/image with explicit width/height dimensions. No raw <img> tags anywhere.
Secrets properly handled. .env.local in .gitignore, never committed. Service role key only used server-side.
Shared tool components extracted: ToolBreadcrumb, ToolHeader, FaqAccordion, ToolCta, tool-schemas.
API input validation on review submit (UUID regex, rating range, length limits, VPN existence check).

Full Issue Index 20 Total
ID Issue Location Severity
vpnmarkt-ts7Race condition in review vote countingapi/review-vote/route.tsCritical
vpnmarkt-efeNo rate limiting on any API routeAll 13 API routesCritical
vpnmarkt-ia7No security headers configurednext.config.tsCritical
vpnmarkt-reoZero error boundaries, loading states, SuspenseEntire src/app/Critical
vpnmarkt-flw13 ESLint errors (component-during-render, setState-in-effect)TestResultsDashboard + 2 moreHigh
vpnmarkt-1s4Homepage metadata in English on German sitelayout.tsx + page.tsxHigh
vpnmarkt-7uuMissing revalidation on 3 dynamic pagespage.tsx, coupons, quizHigh
vpnmarkt-396Zero test infrastructureEntire projectHigh
vpnmarkt-8weSupabase query errors silently swallowedAll page-level queriesHigh
vpnmarkt-7pe543 hardcoded color values in style propsThroughout codebaseMedium
vpnmarkt-ociGod components exceeding 900 lines4 files > 650 linesMedium
vpnmarkt-234No code splitting for heavy client componentsNo next/dynamic usageMedium
vpnmarkt-8fzNewsletter auto-confirms without email (GDPR risk)api/newsletter/subscribeMedium
vpnmarkt-shpDuplicated click tracking aggregation logicvpn/[slug] + couponsMedium
vpnmarkt-6z9274 inline style objects instead of CSS classesThroughout codebaseMedium
vpnmarkt-o5oFont loading optimization opportunitylayout.tsx:8-26Low
vpnmarkt-c4gMissing generateStaticParams for known slugsDynamic [slug] routesLow
vpnmarkt-h6kUnused CSS class .animate-glow-pulseglobals.css:381Low
vpnmarkt-bgjNo favicon variants or PWA manifestpublic/Low
vpnmarkt-jt6Sitemap creates its own Supabase clientsitemap.ts:60Low
20 issues total — 4 critical, 5 high, 6 medium, 5 low
Recommended Fix Priority Order

Week 1 (Critical): Add security headers to next.config.ts. Fix the review vote race condition with an atomic SQL increment. Add root-level error.tsx and loading.tsx. Set up basic rate limiting middleware.

Week 2 (High): Translate homepage metadata to German. Fix ESLint errors (extract SortIcon, refactor setState patterns). Add revalidate to homepage, coupons, and quiz. Start handling Supabase query errors on key pages.

Week 3 (Medium): Set up Vitest + React Testing Library. Write tests for API routes. Extract color constants. Begin breaking up god components. Set up next/dynamic for heavy client components.

Ongoing: Implement double opt-in email flow. Migrate inline styles to CSS classes. Add generateStaticParams. Clean up dead CSS.

End of Audit · VPNMarkt · 7 March 2026