About this audit
allbirds.com scored 5.1/10
Strong storefront, four critical defects. The shopping experience itself is well built — but a dead consent platform, keyboard-unreachable legal policies and a 12.6-second mobile LCP are shipping on every page.
This is a full example audit — 15 evidence-led findings across accessibility, performance, SEO, structured data, privacy and security, each with a measurement, a screenshot and a concrete fix. The headline issue: Consent platform never loads — OneTrust script requests /consent/undefined/ and 404s.
What we found
criticalConsent platform never loads — OneTrust script requests /consent/undefined/ and 404s
Every page requests https://cdn.cookielaw.org/consent/undefined/OtAutoBlock.js. The OneTrust data-domain ID has been interpolated as the literal string undefined, so the request returns 404 and the SDK never initialises. Verified live: window.OneTrust is undefined, and the site's own integration flag reports isOneTrust
criticalPrivacy policy, Terms and Refund policy are keyboard-unreachable and have no URL
All three footer legal links are built as <a role="button" aria-controls="modal-…"> with no href and no tabindex. An anchor without href is not in the tab order — verified live, element.focus() fails to focus all three: <a aria-controls="modal-privacy-policy" role="button" class="cursor-pointer hover:text-white hover:u
criticalMobile LCP is 12,664 ms on the homepage and 10,021 ms on the product page
Lighthouse mobile (lab) scores the homepage 49 and the product page 42. Largest Contentful Paint measures 12,664 ms on the homepage and 10,021 ms on the PDP, against Google's 2,500 ms "good" threshold. Time to Interactive reaches 21,110 ms and 27,067 ms respectively. This is not the unavoidable cost of a rich storefron
criticalbutton-name — the announcement-bar arrows are unlabelled on all 6 pages
axe reports button-name at critical impact on every page audited, 2 elements each, at all three viewports. WCAG 4.1.2 Name, Role, Value (Level A). The offending elements are the previous/next arrows on the announcement bar at the very top of every page — a 24×24 px control at x=0 and x=351 on mobile: <button class="swi
warninglink-name — an empty <a href=""> in the mobile sticky bar reloads the page
axe reports link-name at serious impact on the product page, mobile viewport only. WCAG 2.4.4 / 4.1.2 (Level A). One element: <a href="" class="text-sm leading-tight font-medium tracking-wide uppercase"></a> It sits inside the mobile sticky add-to-cart bar — the product-name slot is rendering empty. Verified live: the
warningaria-required-children — the blog article grid declares role="list" with invalid children
axe reports aria-required-children at critical impact on /blogs/news, at all three viewports. WCAG 1.3.1 Info and Relationships (Level A). One element: <div class="grid grid-cols-1 gap-2.5 sm:grid-cols-2 md:grid-cols-3" role="list" aria-label="Articles"> axe's failure summary: *"Element has children which are not allow
critical139,885 characters of hidden legal markup — 21.1% of every page — ship inside the footer
Three legal modals are inlined into the footer of every page, fully rendered and hidden with display:none: #modal-privacy-policy — 80,682 characters of HTML (335 elements) · #modal-terms-of-service — 55,738 characters (372 elements) · #modal-refund-policy — 3,465 characters (26 elements). Together that is 139,885 chara
warning/blogs/news has no <h1> — the visible title is a <p>
The blog index displays "Allbirds News" over its hero image, but it is marked up as a paragraph: <p class="text-xl">Allbirds News</p> The page's only <h1> element is the hidden Terms-of-Service modal's Table of Contents (issue 7). So the blog index — the entry point to nine press articles — presents no page-level headi
warningProduct page ships 3 visible <h1>s — marketing blocks are headings
The Tree Runner NZ page contains 5 <h1> elements, 3 of them visible: Women's Tree Runner NZ (y=180), Breathable By Nature (y=3,386) and Better Things in a Better Way (y=4,835). A fourth is the desktop-only duplicate of the product title, hidden at mobile by max-md:hidden; the fifth is the Terms modal's Table of Content
warningThe aggregateRating JSON-LD block has no @context and no @type
The product page emits two JSON-LD blocks. The first is a valid ProductGroup. The second is an orphan object: { "aggregateRating": { "@type": "AggregateRating", "itemReviewed": { "@type": "Product", "name": "Women's Tree Runner NZ" }, "ratingValue": "4.2", "reviewCount": "148" } } It declares no @context and no top-lev
warningCollection schema claims 66 and 65 products; the pages render 6 and 8
Both apparel collections declare an ItemList count an order of magnitude above what they show. /collections/mens-apparel — schema "numberOfItems": 66, filter bar reads "(6 products)", and the grid renders 6 SKUs, all socks. /collections/womens-apparel — schema "numberOfItems": 65, filter bar reads "(8 products)", grid
warningHomepage carries no structured data at all — no Organization, no WebSite
The homepage emits zero JSON-LD blocks. Verified in the raw HTML response: no Organization, no WebSite, nothing. The collection pages have CollectionPage, the product page has ProductGroup, the blog index has Blog — the homepage alone has none. For a brand of this profile that is the one page where entity markup matter
warningUnrendered Liquid template code is shipping inside a live srcset
Both apparel collection pages serve a promo banner whose srcset contains raw, unexecuted Liquid: srcset="{{ block.settings.desktop_image | image_url: width: 1600 }} 1600w, {{ block.settings.desktop_image | image_url: width: 1920 }} 1920w" The image still displays because the src fallback resolves — which is exactly why
warningog:image is declared over http:// on all six pages
Every page sets its Open Graph image with an insecure scheme: <meta property="og:image" content="http://www.allbirds.com/cdn/shop/files/logo-seo.jpg?v=1753720776"> The asset is served fine over HTTPS — only the declared URL is http://. Some platforms decline to fetch mixed-scheme preview images, and the risk is highest
warningA third-party app throws 18 identical TypeErrors per collection page load
Loading /collections/womens-apparel produced 18 identical uncaught exceptions from the Redo app extension: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'page') — thrown from cdn.shopify.com/extensions/019fcc14-…/redo-v1.268528/assets/main.js. The pattern of repeats suggests it fires onc