A bilingual (English/Arabic, full RTL) Next.js 16 front-end for Juffali Trucks, a commercial-vehicle dealership listing 240+ trucks, trailers, and other products against a Statamic 4 CMS backend, currently being migrated page-by-page off GraphQL onto a custom, indexed REST API after an internal performance audit found some listing queries taking up to 11 seconds.

The app covers the full dealership funnel: paginated/filterable listings for products, applications/trailers, and other products (brand, body style, color, year, transmission, fuel type, cab type facets), single-product detail pages, offers, free-text search, a wishlist, a branch locator, a 'sell your truck' flow, and a contact form with international phone validation and reCAPTCHA. Every page ships in English and Arabic with next-intl locale routing and a genuinely mirrored RTL layout, not just translated strings.
Built the full storefront frontend: catalog listing, filtering, and vehicle spec pages.
Implemented the filter system — class, model line, and configuration — with state held in the URL.
Set up the image pipeline for vehicle photography: responsive sizes, AVIF/WebP, lazy loading below the fold.
Every trucks/trailers/offers/search/wishlist listing page used to go through Statamic's GraphQL layer — Eloquent hydration, per-row taxonomy lookups, and a legacy endpoint that took 11s cold. A hand-rolled controller now queries entries directly against generated database columns indexed over the JSON taxonomy fields Statamic normally can't index at all. The trade-off: giving up GraphQL's auto-generated schema and Statamic's abstraction for a bespoke, harder-to-maintain PHP controller in a separate backend repo — worth it because the documented latency dropped from ~11s to ~200ms cold / ~55ms warm, and it's now confirmed wired into all four listing consumers with zero remaining GraphQL calls in those files.

The routing config sets English as the default, unprefixed locale (cleaner for SEO in the primary market) while Arabic gets an explicit /ar prefix and a genuinely mirrored RTL layout — nav, logo position, and category-tile order all flip, not just the text direction. The trade-off is a routing/middleware config that has to match both prefixed and unprefixed paths instead of always-prefixed locales; worth it for keeping English canonical while still shipping a real RTL experience.

The audit traced 'missing data / broken page' reports to a product-detail component dereferencing an optional taxonomy field unguarded, which threw whenever a product had no linked body-style term. The shipped fix is a one-line optional-chain — trivial in isolation, but the same audit named a second component with the identical class of cache/render bug, rendered on every product page from this same detail view, and that one is still unfixed: a query object is still rebuilt on every render and an effect still forces a redundant refetch. The trade-off was shipping a fast, targeted patch for the reported crash over a systematic sweep of every file the audit flagged — worth it for the crash, but it leaves a known duplicate-bug class live in a component that renders on every truck detail page.

The CMS stores every entry as a JSON blob in one column, so filtering by brand/body-style/color/year/transmission can't use a database index directly. The backend (a separate repo, not part of this codebase) adds generated columns over each JSON path plus indexes on top, and composite indexes over the collection/site/slug and collection/site/status combinations. The trade-off is schema rigidity — every new filterable field now needs its own generated column and migration — against keeping the CMS fully schemaless; worth it because filtering across 1,500+ rows per collection went from a full JSON scan on every request to an indexed lookup. This decision is documented from this repo's own docs but its implementation lives in the backend repo, so it's reported here as documented context rather than code this repo contains.
Every filter combination is a shareable, bookmarkable link, and the back button behaves as buyers expect. It also means filtered views are server-renderable — no client-side flash of unfiltered content.
Spec pages are statically generated per vehicle and revalidated on inventory change. Buyers on slow connections get full HTML, and every vehicle page is indexable.
Vehicle photos are the product, so they had to stay sharp without wrecking the load: AVIF with WebP fallback, exact responsive sizes per breakpoint, priority-loaded hero image only.
0
unit tests
0
E2E scenarios
Radix UI primitives (dialog, select, checkbox, dropdown-menu, label, switch) back several interactive components, providing keyboard/ARIA semantics by default. Beyond that, hand-written aria-* attributes appear in only 6 non-dead files, and the lint config disables several hook-correctness and a11y-adjacent rules. No skip links, live regions, or systematic focus-management pattern were found.
A Lighthouse run live against the production homepage scored 89/100 performance on desktop (LCP 1.4s, CLS 0, TBT 0ms) but only 39/100 on mobile (LCP 17.3s, CLS 0.831, TBT 180ms) — a large, currently-unaddressed gap. Accessibility scored 84–86/100 and Best Practices 96/100 on both. There is no historical Lighthouse baseline in the repo, so whether this represents improvement or regression versus pre-launch can't be determined — only the current snapshot is reportable. Of six 'quick win' fixes an internal performance audit recommended, verification against current source found 2 fully shipped, 1 shipped only in the file the audit specifically named but not in a sibling component with the identical bug, and 2 not shipped at all. The REST v2 listing API migration itself is real and fully wired into all four listing consumers. There is no automated test suite and no CI pipeline, so none of this — including whether the REST migration itself introduced regressions — is safety-netted by anything beyond manual review.
Visit the live site ↗ (opens external site)This project shows a real, evidence-driven performance response rather than a report that sat unused — the bottleneck audit is specific and live-measured, and it led to an actual backend rewrite (GraphQL listings to a custom indexed REST API), not just a recommendations doc. But the audit's own 'quick win' list is only partially landed: some fixes are exactly as documented, unchanged, and one bug was fixed in the one file the audit named but left in an identical sibling component. There is no test suite and no CI at all, so nothing here — including whether the REST migration itself is safe — is verified by anything but manual review, and the 39/100 mobile Lighthouse score against 89/100 desktop is a live, currently unaddressed gap this case study can point to but the repo offers no explanation for.