Integrations, languages and performance
The site-wide capabilities: the external systems the site connects to, its languages, and how it stays fast at scale.
Integrations
The external systems the Euro League site connects to. Contentful is always on. Most of the rest are switched on by environment: the code ships in this repository, and an integration comes on in an environment (local, develop, staging, production) when that environment sets its key or address. With nothing set, the integration is simply absent — no script loads and nothing is drawn — so a developer's machine and CI run without any of them. The variables are listed in Environment variables.
Content, data & media
| Integration | What it does | Status | Detail |
|---|---|---|---|
| Contentful (CMS) | Where editors author all content (space 12gd2cvfgb6b, "Euro League"); the content model itself is defined in code |
Available (always on) | Content model |
| Competition Engine | ELB's competition data — teams, players, coaches, games, live scores, standings and statistics — behind every basketball page and widget | Available (COMPETITION_ENGINE_BASE_URL) |
@v4/competition-engine |
| Stadion platform video SDK | Supplies video metadata (videos, categories, collections, playlists, filters) for the video section | Available (with video) | Media package |
| Mux | Euro League's video provider: Video Items hold their video in Mux, played through Mux's hosted player; also the muted inline previews | Available | The video section |
| Brightcove | Alternative playback provider (HLS/DASH streaming) | Available | Media and video architecture |
| YouTube | Alternative playback provider, and YouTube embeds in articles | Available | Media and video architecture |
| Scoreplay | ELB's image library and dynamic-crop image CDN — the site's default image source; Contentful's image service serves images uploaded there instead | Available | @v4/image |
| WSC Sports (Stories / Moments) | The clips rows: an editor's WSC block, and the team and player pages; their events go to the dataLayer, with consent | Available (NEXT_PUBLIC_WSC_API_KEY) |
@v4/wsc |
| EuroLeague TV (Deltatre Vesper SDK) | The Game Centre links out to EuroLeague TV. An embedded livestream player is built but not yet placed on any page | In development (NEXT_PUBLIC_VESPER_API_KEY) |
@v4/eltv |
| ECAL | "Add to calendar" on the Games page: a competition's or a team's schedule into the fan's calendar | Available (NEXT_PUBLIC_ECAL_API_KEY) |
@v4/ecal |
Fans, measurement & consent
| Integration | What it does | Status | Detail |
|---|---|---|---|
| Google Tag Manager | Loads ELB's GTM container; the site pushes the tracking plan's events (page views and interactions) to the dataLayer. Every tag lives in ELB's container — the site loads no GA4 or gtag.js itself | Available (NEXT_PUBLIC_GTM_CONTAINER_ID) |
@v4/analytics |
| OneTrust | The cookie banner and consent choices, passed on through Google Consent Mode v2. No event is sent before the visitor consents | Available (NEXT_PUBLIC_ONETRUST_DOMAIN_SCRIPT_ID) |
@v4/analytics |
| Cortex (EuroLeague ID sign-in) | Single sign-on: a Login control in the site header, the signed-in fan's session, and the sign-in the Code of Conduct report requires. Not yet used to unlock any video | Available (CORTEX_SSO_BASE_URL) |
@v4/cortex-sso |
| Email (Code of Conduct reports) | Sending a submitted In-Arena Code of Conduct report to ELB. No email provider is chosen yet: a local site logs the email, and any other environment answers that it cannot send | Not built | @v4/code-of-conduct |
| Visitor country | The footer's sponsor rules by country, read from the request's headers (/api/geolocation), never cached |
Available | — |
Hosting & caching
| Integration | What it does | Status | Detail |
|---|---|---|---|
| Redis | A shared cache, so one editorial change reaches every running copy of the app | Available (CACHE_REDIS_URL) |
Caching at scale (Redis) |
| Cloudflare | Edge caching of listings and home pages, purged by tag when an editor publishes | Available (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID) |
Edge caching & purge |
Configuration slots with nothing behind them
The configuration schema (packages/template-config/src/schema.ts) has slots for these, but nothing in this site reads them: setting one switches nothing on. Each would be a build, not a configuration change.
| Slot | What it would be | Status |
|---|---|---|
| Search (Algolia) | Site search | Not built |
| Realtime (Ably) / live match (Pulselive) | A realtime feed and broadcaster data | Not built — live scores come from the Competition Engine instead |
| Analytics with GA4 directly | GA4 loaded by the site | Not built — by design, GA4 is configured inside ELB's GTM container |
| Error monitoring (Sentry) | Runtime error tracking | Not built |
| Ads (Google Publisher Tag) | Ad slots | Not built |
| Site ticker | A route-targeted ticker strip | Not built in the site — editors can fill it in Contentful, but no page draws it |
| Gamification | Polls, quizzes, fan engagement | Not built |
How a feature is declared, and how an environment variable switches it on, is governed by the typed configuration file apps/web/club.config.ts. See Template config and Features & domains.
Design & tokens
| Integration | What it does | Status | Detail |
|---|---|---|---|
| Figma (Ghost 2.0 and ELB's files) | The components are built from Ghost 2.0 and ELB's own Figma files — ELB – Web – Core, Content and Basketball, and the EXT concept file — and their designs are checked against them | Available (build-time) | Token pipeline |
| Token Studio | ELB's designers push the brand's design tokens (colours, type, the EuroCup and NextGen themes) straight into the repository, where they are generated into @v4/tokens-euro-league |
Available (build-time) | @v4/tokens-euro-league |
Figma and Token Studio are build-time integrations, not runtime ones — designers work in Figma, and the design tokens flow into the codebase through the token pipeline. They underpin theming and the whole component library, but the live site never calls Figma.
Internationalisation
The site is multi-locale from the ground up. It currently uses one locale, but locale-prefixed addresses, translatable interface copy and the process for adding a language are all Available; full right-to-left (RTL) language support has its foundations in place but is Planned.
Locales
The site can serve content and interface copy in several languages. Euro League runs one locale, en-US — the default and the only one (apps/web/club.config.ts → locales) — and new locales are added through a defined process, not a code rewrite.
Every address carries its locale first: en-US is served under /en, so the home page is /en, an article /en/news/{article} and a team /en/teams/{team}. An address without the locale (/news) redirects to the one with it. This is the segment routing mode in the configuration (the other mode, with no locale in the address, is for a single-locale site that does not want one).
| Capability | Status | Detail |
|---|---|---|
| Locale-prefixed addresses | Available (/en/…) |
Competition sections |
| Multiple content locales | Available (the site uses one) | Localisation |
| Per-locale content in Contentful | Available | Localisation |
| Adding a new locale | Available (defined process) | Localisation |
The dictionary still carries label sets for en-GB, fr-FR and it-IT inherited from the template, with translation passes outstanding. They are not served by this site; confirm their state before promising a translated Euro League site in another language.
UI copy: the dictionary
Interface labels — button text, section titles, standard phrases — are managed through a dictionary. The code owns the vocabulary (the set of keys and their default English copy) in code; editors maintain the labels in Contentful, and a label they publish reaches the site without a deploy. A label is looked for in the requested locale, then in en-US, then in the default copy in code. This means the interface is fully localisable without a developer, while the set of things that can be localised stays typed and reviewed.
| Capability | Status | Detail |
|---|---|---|
| Translatable UI copy | Available | Dictionary keys |
| Editor-maintained labels | Available | Dictionary keys |
| Promoting translations between environments | Available | Dictionary keys |
Right-to-left (RTL)
The design and code layers anticipate RTL: the layout uses direction-aware CSS throughout, and the token layer ships a complete RTL typography set. But the design system's components were authored left-to-right only, so RTL is an engineering effort still to be completed rather than a finished capability.
| Capability | Status | Detail |
|---|---|---|
| Direction-aware layout foundations | Available | RTL & motion |
| RTL typography tokens | Available | Token pipeline |
| Full RTL language support (shipped RTL locale) | Planned | RTL & motion |
In short: if ELB asks for an RTL language (Arabic, Hebrew), the honest answer is that the site is built to support it and the groundwork is done, but shipping an RTL locale is a project, not a switch.
Performance & scale
The site is built to serve high traffic cheaply and fast. Everything here is Available today.
Static-first rendering
Every page is served as a pre-rendered shell with cached data: the parts of a page that do not change per visitor are built ahead, and the content inside them is read through the app's cache rather than from Contentful on every request. Listings are path-based — /en/news/page/2 and every filtered view are pages of their own — so they stay cacheable under filtering. Listings and home pages are also cached at the Cloudflare edge, in front of the app; article and content detail pages are served by the app itself, from its cache.
Basketball data is cached by how fast it changes: team lists, profiles, standings and statistics for five minutes, the schedule and an unplayed game for one minute, a finished game for five. Live answers are never cached — while a game can be live, the browser asks the site for it directly (see Sport).
| Capability | Status | Detail |
|---|---|---|
| Pre-rendered shells, cached data | Available | Caching & revalidation |
| Edge caching (Cloudflare) | Available, switched on by environment | Edge caching & purge |
| Path-based (static) pagination | Available | Pagination & filtering |
Targeted cache invalidation
Rather than rebuilding the site on every edit, the site refreshes exactly the pages a change affects. When an editor publishes, a webhook expires only the relevant cached content, and purges the same pages from the Cloudflare edge by tag; time-based limits act as a self-healing floor if a webhook is ever missed. The result is near-instant freshness with minimal recompute.
| Capability | Status | Detail |
|---|---|---|
| On-demand, tag-based revalidation | Available | Caching & revalidation |
| Self-healing time-based fallback | Available | Caching & revalidation |
| Edge purge by tag on publish | Available, switched on by environment | Edge caching & purge |
Multi-instance caching
Each deployed environment runs the app across several server instances, so it uses a shared Redis cache that lets one editorial change reach every replica. It is config-gated (on where CACHE_REDIS_URL is set) and isolated per environment — each environment has its own Redis, never shared — and off where it is unset (a single local instance needs nothing extra).
| Capability | Status | Detail |
|---|---|---|
| Redis cache handler | Available, config-gated | Caching at scale (Redis) |
| Cache isolation per environment | Available (by design) | Scale & performance |
Lean pages
A Lighthouse campaign in October 2026 cut what each page has to download and lay out. The site's styles are split by section: every page loads a shared stylesheet of about 140 KB compressed (it was one 3 MB file), and the video and basketball pages each add their own smaller part only where they are used. The brand's design values are declared once for the whole page rather than on every component, which cut style and layout work on the home page by roughly two-thirds.
A load-test script (scripts/loadtest/eviction-storm.mjs) checks that a mass cache refresh under heavy traffic is absorbed by serving slightly stale pages while fresh ones are built, rather than flooding Contentful. It is run against a real deployment with Redis, not in CI.
| Capability | Status | Detail |
|---|---|---|
| Per-section stylesheets | Available | Scale & performance |
| Load test for mass cache refresh | Available (run by hand) | Scale & performance |
SEO & discoverability
| Capability | Status | Detail |
|---|---|---|
| Per-page metadata (titles, descriptions, social cards) | Available | SEO & metadata |
| Sitemaps | Available | Sitemaps |
| Custom error & not-found pages | Available | SEO & metadata |
The scale story in one line
Pre-rendered pages with cached data, refreshed precisely when content changes, cached at the edge and shared across instances — so traffic spikes hit the cache, not the CMS or the Competition Engine. For the full picture see Scale & performance.