Content Management

The website consumes content from a Strapi v5 headless CMS via a server-side API client (src/lib/strapi.ts). All data fetching happens at build time or request time with Incremental Static Regeneration (60-second revalidation).

Strapi API Client

The client lives at src/lib/strapi.ts and provides typed functions for each content type. Every request appends a public filter (filters[publishTarget][$contains]=public) to ensure only published, public-facing content is served.

Configuration

Variable

Description

STRAPI_URL

Strapi base URL (default: http://localhost:1337)

REVALIDATE

ISR interval in seconds (hardcoded: 60)

PUBLIC_FILTER

Applied to all queries automatically

Available Functions

Pages:

  • fetchNavPages(locale) — Pages with showInNav=true, sorted by sortOrder

  • fetchPublicPages(locale) — All public pages

  • fetchPublicPage(slug, locale) — Single page by slug

Articles:

  • fetchPublicArticles(page, pageSize, locale, categorySlug?, tagSlug?) — Paginated article listing with optional filters

  • fetchPublicArticle(slug, locale) — Single article by slug

  • fetchFeaturedArticles(locale, limit?) — Featured articles

Categories & Tags:

  • fetchCategories(locale?) — All categories sorted by sortOrder

  • fetchTags() — All tags sorted by name

Media Collections:

  • fetchPublicMediaCollections(page, pageSize, locale)

  • fetchPublicMediaCollection(slug, locale)

Helpers:

  • strapiMediaUrl(media) — Resolves absolute or relative media URLs

Content Types

All types are defined in src/lib/types.ts.

Article

Field

Type

Description

title

string

Article title

slug

string

URL slug (unique per locale)

summary

string?

Short description for listings

content

string

Full markdown body

coverImage

StrapiMedia?

Featured image

author

Author?

Author relation

category

Category?

Single category relation

tags

Tag[]

Many-to-many tag relations

publishTarget

string[]

Visibility (["public"])

featured

boolean

Show in featured sections

locale

string

Content locale (en, zh-CN, ja)

publishedAt

string?

ISO date of publication

Page

Field

Type

Description

title

string

Page title

slug

string

URL slug (e.g., home)

metaDescription

string?

SEO meta description

sections

Section[]

Dynamic zone (Hero, FeatureGrid, etc.)

publishTarget

string[]

Visibility

sortOrder

number

Navigation ordering

showInNav

boolean

Show in navbar

locale

string

Content locale

Category, Tag, Author

  • Categoryname, slug, description, sortOrder, localizations

  • Tagname, slug

  • Authorname, bio, avatar, keycloakUserId

CMS-Driven Pages

Pages fetched from Strapi use a dynamic zone pattern. Each page has a sections array where each entry has a __component string identifying which React component to render. The SectionRenderer component dispatches accordingly:

// src/components/sections/SectionRenderer.tsx
const componentMap = {
  "page-sections.hero": HeroSection,
  "page-sections.feature-grid": FeatureGridSection,
  "page-sections.rich-text": RichTextSection,
  "page-sections.call-to-action": CallToActionSection,
  "page-sections.image-gallery": ImageGallerySection,
};

If the CMS returns no home page, the site falls back to StaticHomePage — a Figma-designed homepage with hero, features, partner logos, and CTA sections.