Independent teardown

Site review & fixes

An independent review of the old lionsville.nl, published whole — and the record of what the rebuild did about every finding in it.

Reviewed
23 August 2026
Pages sampled
9
Stack reviewed
WordPress.com Atomic · nginx · HTTP/2
Tools
axe-core 4.x, headless Chromium, raw HTTP

What this page is

In August 2026 an independent review of lionsville.nl was commissioned. The brief was to test the site as it then stood — performance, accessibility, SEO, privacy, security and platform hygiene — against 2026 web practice, and to report without hedging. It found fourteen problems and graded eight areas.

The site was rebuilt from scratch in response. This page is the public record of both halves: the review as it was written, and what the rebuild did about it.

Nothing below is softened. The findings appear at the reviewer’s severity, in the reviewer’s wording, with the measurements the reviewer cited. Where the rebuild did not fully close something — the contact form, HTTP/3 — the response says so rather than claiming a fix.

The design is fine. The plumbing is the problem.

The review’s masthead, 23 August 2026

The verdict

Visually it holds up: the lion, the purple-on-black, the display type read as a confident boutique, not a template. Nothing in the review was about taste.

What was wrong was everything behind the paint: Google Analytics fired before consent; the cookie banner linked to a page called “Testing layout”; WordPress attachment pages sat in the sitemap with filenames as headlines; the homepage shipped 2.9 MB, mostly PNGs that should be a tenth the size; 16 of 19 images had empty alt text; no CSP, no Referrer-Policy, no Permissions-Policy.

The gap between the claim — “advanced engineering solutions” sold to financial services and government — and the artifact was the real finding.

Independent teardown of lionsville.nl, 23 August 2026

The scorecard

Eight areas, graded as they stood on 23 August 2026. One of the eight came back strong, and it is reproduced here with the rest.

The review’s scorecard: eight areas of the old site, each with a grade and the reviewer’s one-line note.
AreaGradeThe reviewer’s note
Visual designStrongDistinctive, consistent, well-executed brand system.
Privacy & consentFailingAnalytics runs pre-consent; policy link points to a test page.
PerformanceWeak2.9 MB homepage, 2.6 MB of it unoptimised imagery.
AccessibilityWeakEmpty alt text throughout; contrast and target-size failures.
SEO & metadataWeakJunk pages indexed, no structured data, 4-word description.
Security postureThinHSTS only. No CSP, Referrer-Policy or frame protection.
Content & copyUnevenTypos in the About page’s first sentence, which Google quotes.
Platform hygieneFragileThree layout engines stacked; duplicate pages left live.

Ship-stoppers

Three findings the review graded critical. All three were live on the day it ran.

Finding 1Severity: Critical

Google Analytics loads before consent.

Complianz was configured for EU opt-in and correctly blocked Jetpack Stats (type="text/plain" + data-cmplz-src), but Site Kit’s gtag.js sat in the head as a live script and called gtag("config", …) immediately. Zero gtag('consent','default',…) calls anywhere. A visitor landed, GA set identifiers and sent a page_view, and only then were they asked. Under Dutch Telecommunicatiewet 11.7a and GDPR the ordering is the violation.

Evidence cited

Jetpack Stats
Blocked correctly — type="text/plain" with data-cmplz-src
Site Kit gtag.js
Live script in the head, calling gtag("config", …) immediately
Consent Mode defaults
Zero gtag('consent','default',…) calls anywhere on the site
Order of events
Identifiers set and page_view sent, then the banner shown
Law cited
Telecommunicatiewet 11.7a and GDPR

How it was fixed

Fixed. No third-party script is in the document. Consent Mode v2 defaults are set to denied before anything runs; gtag.js (GT-WBTCR2CL) is injected only once statistics consent is granted, and withdrawing consent pushes analytics_storage: 'denied' straight back.

Finding 2Severity: Critical

The cookie policy link went to a page called “Testing layout.”

Complianz hard-coded page_links.eu.cookie-statement.url to https://lionsville.nl/2025/05/06/testing-layout/ — a May 2025 blog post, live, HTTP 200, no noindex, present in /sitemap-1.xml. The banner rendered it as an anchor with no text at all: the site’s one link-name accessibility failure, a focusable link a screen reader announced as nothing.

Evidence cited

Configured value
page_links.eu.cookie-statement.url = https://lionsville.nl/2025/05/06/testing-layout/
Target
A blog post titled “Testing layout”, May 2025
Status of that page
HTTP 200, no noindex, listed in /sitemap-1.xml
Anchor text
None — axe-core link-name, serious, ×1 (the site’s only instance)

How it was fixed

Fixed. A real /cookie-statement/ page was written and is linked from the banner and the footer. /2025/05/06/testing-layout/ now 301s to it and is out of the sitemap. The banner link has visible text.

Finding 3Severity: Critical

WordPress attachment pages indexed as content.

Five in the sitemap, self-canonicalising, raw filename as the <h1> (/financial-services/ <h1>financial-services</h1>, body.attachment). /our-work/ and /our-work-2/ were both live, both titled “Our work”, each canonicalising to itself. The homepage carried no canonical tag at all. The trap: /financial-services/ was the junk attachment page while /financial-services-2/ was the real sector page.

Evidence cited

Attachment pages in the sitemap
5, each canonicalising to itself
Headline rendered
<h1>financial-services</h1> on body.attachment
Duplicate pages
/our-work/ and /our-work-2/, both live, both titled “Our work”
Homepage canonical
Absent
The trap
/financial-services/ was the junk page; /financial-services-2/ was the real one

How it was fixed

Fixed. Attachment pages do not exist in a React app. /financial-services/ and /government-public-services/ 301 to the real sector pages; /our-work-2/ 301s to /our-work/. Every page has a self-referential canonical, homepage included.

Should fix

Five findings the review graded high: the weight of the page, and everything about it a visitor could feel.

Finding 4Severity: High

2.9 MB homepage, 2.6 MB of it images.

Everything served as JPEG or PNG, no format negotiation (Accept: image/avif,image/webp still returned PNG). Jetpack’s CDN was preconnected and referenced 34 times, yet only 2 of 19 <img> used it. The five sector tiles were photographic artwork saved as PNG — roughly 1 MB for five 400px squares that should be ~100 KB as WebP. No srcset, so a phone downloaded desktop bytes.

Evidence cited

header-image-lv-kopie.jpg             784 KB   1536×1024 hero
blog-lv.jpg                           568 KB
manufacturing-400x400.png             240 KB
transport-logistics-400x400.png       231 KB
financial-services-400x400.png        210 KB
fashion-retail-400x400.png            205 KB
government-public-services….png       166 KB
Lionsville_icon_only.png              119 KB   1000×1000
--------------------------------------------
Sub-resources, total                2,784 KB
HTML                                  198 KB
Format negotiation
None — Accept: image/avif,image/webp still returned PNG
Jetpack CDN
Preconnected and referenced 34 times, used by 2 of 19 <img>
Responsive images
No srcset — a phone downloaded desktop bytes

How it was fixed

Fixed. Every image is generated at build time as AVIF + WebP at up to six widths — capped by the source, never upscaled — with a JPEG/PNG fallback, served through srcset/sizes. The 784 KB hero is now ~39 KB as AVIF at 1280px; the sector tiles land between 4 KB and 26 KB at the widths their cards actually request.

Finding 5Severity: High

Sixteen of nineteen images had empty alt text.

Every client logo (Vopak, Australia Post, Decathlon, EDSN, Liquin, Loodswezen, PVH, Primevision) and every sector tile shipped alt="". Only the three Lionsville logo instances had real alt text. The logos are the credibility section.

Evidence cited

Images with empty alt
16 of 19, all shipping alt=""
Client logos affected
All eight — Vopak, Australia Post, Decathlon, EDSN, Liquin, Loodswezen, PVH, Primevision
Sector tiles affected
All five
Images with real alt text
The three Lionsville logo instances, and nothing else

How it was fixed

Fixed. Every image carries descriptive alt text; the eight client logos are named.

Finding 6Severity: High

WCAG 2.2 AA failures.

axe-core across four pages, desktop and mobile: aria-required-parent (critical, ×5) — slider tabs with role="tab" and no role="tablist" parent; target-size (serious, ×5) — slider dots 11×11px against a 24×24 minimum; color-contrast (serious, ×4) — #6940B1 on #000000 = 2.96:1 where 3:1 was required; link-name (serious, ×1) — the cookie-statement anchor; and a skip link rendered at 1×1px. The contrast failure was brand purple on brand black, used for section headings on About and Contact.

Evidence cited

aria-required-parent   critical  ×5  role="tab" with no role="tablist" parent
target-size            serious   ×5  slider dots 11×11px against a 24×24 minimum
color-contrast         serious   ×4  #6940B1 on #000000 = 2.96:1 where 3:1 required
link-name              serious   ×1  the cookie-statement anchor
Scope
Four pages, desktop and mobile, axe-core 4.x
Skip link
Present, but rendered at 1×1px
Where the contrast failure showed
Brand purple on brand black, used for section headings on About us and Contact

How it was fixed

Fixed. The carousel is gone, so aria-required-parent and the 11×11px dots go with it. All targets clear 44px. The heading purple was lifted from #6940b1 (2.96:1) to #8a5cdb (4.63:1) — same hue, passes AA for normal text. The skip link is a real, visible-on-focus control.

Finding 7Severity: High

The sector carousel rendered a fourth card sliced in half.

At 1440px the track was 2,937px wide inside a 1,440px viewport; last visible tile’s right edge at 2,242px. Same at 412px (track 843px, edge 484px). Clipped mid-image, no fade, no gutter. Recommendation: whole cards per breakpoint or a deliberate peek — and note the five sectors are the most saleable thing on the page, hidden behind a carousel most visitors never advance. A static grid would likely convert better.

Evidence cited

At 1440px viewport
Track 2,937px wide; last visible tile’s right edge at 2,242px
At 412px viewport
Track 843px wide; last visible tile’s right edge at 484px
Treatment of the overflow
Clipped mid-image, no fade, no gutter
Reviewer’s note
The five sectors are the most saleable thing on the page, hidden behind a carousel most visitors never advance

How it was fixed

Fixed. Replaced with a responsive static grid — all sectors visible, each one a link to its sector page.

Finding 8Severity: High

Body text was 13px and ran 140 characters wide.

Most common size 13.2px desktop (68 elements), 12.8px mobile (72 elements). The homepage intro paragraph spanned the full ~1,280px container, roughly 140 characters per line against the 60–75 that keeps a reader tracking. Fix: 16–18px body, cap prose at ~68ch.

Evidence cited

Most common body size, desktop
13.2px, on 68 elements
Most common body size, mobile
12.8px, on 72 elements
Homepage intro line length
~140 characters, across the full ~1,280px container
Range the reviewer asked for
60–75 characters per line

How it was fixed

Fixed. Body type is fluid 17–19px; prose is capped at 68ch via a --measure token.

Worth doing

Six findings the review graded medium — the hygiene layer, where nothing was on fire and everything was missing.

Finding 9Severity: Medium

Security headers: HSTS and nothing else.

strict-transport-security: max-age=31536000 (no includeSubDomains, no preload). Absent: content-security-policy, x-content-type-options, referrer-policy, permissions-policy, x-frame-options. /.well-known/security.txt returned 404.

Evidence cited

strict-transport-security   max-age=31536000
                            (no includeSubDomains, no preload)
content-security-policy     absent
x-content-type-options      absent
referrer-policy             absent
permissions-policy          absent
x-frame-options             absent
/.well-known/security.txt   HTTP 404

How it was fixed

Fixed. public/_headers ships CSP, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, X-Frame-Options, COOP and a hardened HSTS, with an equivalent nginx snippet in the repo. /.well-known/security.txt is published.

Finding 10Severity: Medium

No structured data, and a four-word meta description.

Zero application/ld+json blocks on any page sampled. Homepage description was the tagline verbatim (“The Business Tech Boutique”); the About page’s was auto-generated from body copy, complete with a raw YouTube URL and an &hellip; mid-sentence. og:image was a WordPress.com auto-generated fallback.

Evidence cited

Structured data
Zero application/ld+json blocks across all 9 pages sampled
Homepage description
“The Business Tech Boutique” — four words, the tagline verbatim
About us description
Auto-generated from body copy, containing a raw YouTube URL and an &hellip; mid-sentence
og:image
A WordPress.com auto-generated fallback

How it was fixed

Fixed. Organization + WebSite JSON-LD site-wide, Article/Event JSON-LD where they apply, and a hand-written meta description and Open Graph card per page.

Finding 11Severity: Medium

“loT” appeared three times on the About page. “IoT” appeared zero times.

Lowercase L instead of capital I, in the sentence that opens the page — and therefore the sentence Google quoted. Same sentence had “it’s encompassing technology” where it needed “its”.

Evidence cited

“loT” on the About page
3 occurrences
“IoT” on the About page
0 occurrences
Second error, same sentence
“it’s encompassing technology”, where the possessive “its” was meant
Where it sat
The opening sentence — the one Google quoted in results

How it was fixed

Fixed. Corrected in the copy, and the About page’s description is now hand-written rather than an auto-excerpt containing a raw YouTube URL.

Finding 12Severity: Medium

Three layout engines stacked on one site.

wp-theme-koinonia is-block-theme, metaslider-plugin, elementor-default elementor-kit-… all rendering together: 27 inline <style> blocks totalling 103 KB, 69 declared font-family presets for a site that used two, plus render-blocking jQuery and FlexSlider in the head.

Evidence cited

Engines rendering together
wp-theme-koinonia is-block-theme, metaslider-plugin, elementor-default elementor-kit-…
Inline style blocks
27, totalling 103 KB
Declared font-family presets
69, for a site that used two
Render-blocking in the head
jQuery and FlexSlider

How it was fixed

Fixed. One system: React components with colocated CSS and design tokens. No inline style blocks, two font families, no render-blocking JavaScript.

Finding 13Severity: Medium

The contact page had no form.

Nine pages sampled, zero forms site-wide; only mailto:info@lionsville.nl and a phone number. Fine as a deliberate signal, but nothing is captured from a visitor not ready to compose an email, and there is no attribution for inbound.

Evidence cited

Pages sampled
9
Forms found site-wide
0
Contact routes offered
mailto:info@lionsville.nl and a phone number
Consequence the reviewer named
Nothing captured from a visitor not ready to compose an email, and no attribution for inbound

How it was fixed

Addressed. The contact page states the deliberate choice and keeps the direct email and phone routes prominent.

Finding 14Severity: Medium

Loose ends.

A closing list of smaller things the review noted across the site, none of them fatal on its own.

Evidence cited

  • lang="en-US" and og:locale en_US on a .nl domain with no Dutch version.
  • Google Fonts loaded from fonts.gstatic.com.
  • HTTP/2 only, no HTTP/3 (platform-limited).
  • The consent banner weighted Accept (filled) over Deny (outlined), where the Dutch AP expects refusing to be as easy and as prominent as accepting.
  • No cookie-preferences link in the footer to withdraw consent later.
  • No web app manifest and no theme-color.

How it was fixed

Fixed. lang="en", og:locale en_GB, self-hosted Outfit and Thicker (no fonts.gstatic.com), equal-weight Accept/Deny, a footer “Cookie preferences” control to withdraw consent, a web app manifest and theme-color. HTTP/3 remains a hosting choice.

The original priority list

The review closed with a shortlist, headed “if you only do six things”.

  1. Gate Google Analytics behind consent.
  2. Fix the cookie-statement link and delete the “Testing layout” post.
  3. Compress the images — 2.9 MB to under 700 KB with no design change.
  4. Set body text to 16–18px and cap line length at ~68ch.
  5. Write alt text for the client logos and sector tiles.
  6. Add the four missing security headers and Organization JSON-LD.

What was already right

The review’s own list of what the old site got right, reproduced in full. It is as much a part of the record as the failures.

  • The brand system. Coherent, memorable, not a template.
  • Layout stability. Explicit width and height on every image, fetchpriority="high" and a srcset on the hero, 15 of 19 images lazy-loaded — near-zero CLS.
  • Caching and compression. Brotli on CSS and JS, one-year immutable cache headers on static assets.
  • Transport. HTTP → HTTPS, www → apex, HSTS present.
  • Crawlability basics. Valid robots.txt, working sitemap index, one <h1> per page, sensible heading order, a skip link and aria-labels on slider controls.
  • Dutch legal footing. KVK number, VAT number and registered address in the footer, plus a published Accessibility Statement.
  • Consent architecture existed. Complianz was set to EU opt-in and did block Jetpack Stats. The machinery was right; Google Analytics simply was not wired into it.

Method & caveats

Findings came from the live site on 23 August 2026: raw HTTP for headers, payload sizes and redirect behaviour; headless Chromium at 1440×900 and 412×915 (Pixel 7) with axe-core 4.x.

Pages sampled: homepage, About us, What we do, Contact, Our work (both), Careers, and two attachment pages — nine in all.

The review’s legal points — the Telecommunicatiewet and GDPR references under finding 1, and the Autoriteit Persoonsgegevens expectation under finding 14 — were engineering observations, not legal advice.