架构

技术栈

层级

技术

框架

Next.js 15 (App Router)

UI 库

React 19

语言

TypeScript 5.7

设计系统

IBM Carbon Design System (@carbon/react)

样式

SCSS + Carbon 令牌

Markdown

react-markdown + remark-gfm + remark-math

数学公式渲染

KaTeX (rehype-katex)

图表

Mermaid.js

测试

Vitest + React Testing Library + jsdom

CMS

Strapi v5 (无头模式)

项目结构

www/
├── public/                    Static assets (fonts, images)
│   ├── fonts/                 Custom typeface (SyriusSans)
│   └── images/                Homepage assets, blog placeholder
├── scripts/                   Seed and mock scripts
│   ├── mock-strapi.mjs        Mock Strapi v5 server
│   ├── seed-blog-articles.mjs Seed 9 articles to real Strapi
│   ├── seed-blog-content.mjs  Article content definitions
│   └── seed-blog-edge-cases.mjs  11 edge-case test articles
├── src/
│   ├── app/                   Next.js App Router pages
│   │   ├── [locale]/          Locale-scoped routes
│   │   │   ├── blog/          Blog listing, detail, filters
│   │   │   ├── gallery/       Gallery listing, detail
│   │   │   └── [slug]/        CMS-driven dynamic pages
│   │   ├── robots.ts          SEO robots.txt
│   │   └── sitemap.ts         Dynamic sitemap generator
│   ├── components/            React components
│   │   ├── homepage/          Static homepage fallback
│   │   └── sections/          CMS section renderers
│   ├── lib/                   Core utilities
│   │   ├── strapi.ts          Server-side Strapi API client
│   │   ├── types.ts           TypeScript interfaces
│   │   ├── i18n.ts            Locale config + translations
│   │   └── consoleUrl.ts      Runtime console/stargate URL derivation
│   ├── styles/                SCSS stylesheets
│   │   ├── _tokens.scss       Carbon Design imports
│   │   ├── globals.scss       Root styles and themes
│   │   ├── layout.scss        Layout components
│   │   ├── homepage.scss      Homepage styles
│   │   └── sections.scss      Page section styles
│   ├── middleware.ts           Locale routing middleware
│   └── __tests__/             Vitest unit tests
└── docs/                      This documentation (Sphinx)

路由

所有路由均带有语言前缀。src/middleware.ts 中的中间件执行以下逻辑:

  1. /_next/api/fonts 或静态文件的请求直接放行

  2. 如果路径的第一段是有效的语言代码(enzh-CNja),则请求直接放行

  3. 否则,从 Accept-Language 请求头中检测用户的首选语言,并将用户重定向到 /{locale}{path}

路由映射

路由

描述

/[locale]

首页(CMS 内容或静态回退页面)

/[locale]/blog

博客列表(分页,每页 5 篇)

/[locale]/blog/[slug]

文章详情页

/[locale]/blog/categories/[slug]

分类视图(该分类下的所有文章)

/[locale]/blog/tags/[slug]

标签筛选(分页,每页 5 篇)

/[locale]/gallery

图库列表(分页,每页 12 项)

/[locale]/gallery/[slug]

图库合集详情

/[locale]/[slug]

CMS 驱动的动态页面

组件

布局组件

  • Navbar --- Header with logo, nav links, theme toggle, locale switcher, StarGate admin sign-in link, login button, mobile hamburger menu. The admin link (adminUrl) points to stargate.{domain} and is derived at runtime from the current hostname (see consoleUrl.ts below), so it automatically resolves to the correct partition (.ai or .com).

  • Footer --- 3 accordion sections (Platform, Services, Company), legal links, copyright. The "Services" section includes a "My Account" link (to StartPoint) and an "Admin" link (to StarGate), both using runtime-derived URLs.

  • ThemeProvider --- Dark/light mode context (white / g100 Carbon themes). Persists the user's choice to a cross-subdomain cookie (fg-theme, scoped to .flexgalaxy.ai or .flexgalaxy.com) so the preference carries over to console apps. Falls back to localStorage and then prefers-color-scheme media query.

  • LocaleSwitcher --- Language dropdown preserving current path. On mount and on every language switch it writes a cross-subdomain cookie (fg-lang) so other apps on the same base domain can pick up the preferred locale.

  • CookieConsent --- GDPR 横幅,提供必要/性能/广告 Cookie 开关

内容组件

  • ArticleCard --- 博客预览卡片,包含封面图、分类链接、标题、摘要和标签。通过 ::after 伪元素覆盖层实现整张卡片可点击

  • RichMarkdown --- Markdown 渲染器,支持 GFM、KaTeX 数学公式、原始 HTML 和 Mermaid 图表

  • MermaidBlock --- 延迟加载 Mermaid 库,使用 Carbon 蓝色主题渲染 SVG,出错时回退显示原始代码

  • CategoryArticleViewer --- 分类页面布局,包含侧边栏文章列表、文章内容区域和目录侧边栏

  • Pagination --- 上一页/下一页按钮及页码指示器

  • ErrorState --- 将 HTTP 状态码映射为本地化错误页面

CMS 区块组件

SectionRenderer 负责分发 Strapi 动态区域组件:

__component

组件

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

样式

所有样式均使用 SCSS 编写,并通过 _tokens.scss 导入 IBM Carbon Design 令牌。定义了以下四个响应式断点:

令牌

宽度

$bp-sm

320px

$bp-md

672px

$bp-lg

1056px

$bp-xlg

1584px

主题切换通过 <html> 元素上的 data-carbon-theme 属性以纯 CSS 方式驱动,在 white``(浅色)和 ``g100``(深色)Carbon 主题之间切换。所有颜色均引用 CSS 自定义属性(例如 ``var(--cds-text-primary))。

Runtime URL Derivation

External URLs (Console, StarGate) are derived at runtime from the browser's current hostname, not from build-time environment variables. This is implemented in src/lib/consoleUrl.ts via the useConsoleUrls() React hook.

The hook inspects window.location.hostname to determine:

  1. Base domain --- the last two segments of the hostname (e.g., flexgalaxy.ai or flexgalaxy.com)

  2. Environment prefix --- dev- if the hostname starts with dev-, empty otherwise

It then constructs:

  • startUrl = https://{prefix}console.{baseDomain}/start/

  • signupUrl = https://{prefix}console.{baseDomain}/start/?register=true

  • adminUrl = https://{prefix}stargate.{baseDomain}

This means the same build artifact works on both .ai (international) and .com (China) deployments without rebuild. The NEXT_PUBLIC_START_URL and NEXT_PUBLIC_SIGNUP_URL environment variables serve only as SSR fallbacks (used during server-side rendering before hydration).