[refactor] migrate feasible app page components from Client to Server for SEO#9
Conversation
[add] Traditional Chinese translation data [optimize] update Upstream packages
There was a problem hiding this comment.
Pull request overview
This PR aims to improve SEO and shareability of the three “list” surfaces by moving search/filter/pagination state into URL-driven server-rendered pages, and by adding SSR language selection/fallback for list-page UI text.
Changes:
- Reworked
/activities,/restaurants, and/tutoringlist pages to be server components with GET-based search and URL pagination (page size = 10). - Introduced SSR language loading via
mobx-i18n+ new translation maps forzh-CN,zh-TW, anden-US. - Aligned activities data fetching by adding
fetchActivitiesCatalog()and reusing it in the API route and activities pages.
Reviewed changes
Copilot reviewed 20 out of 22 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| public/locales/zh-TW/translation.json | Adds Traditional Chinese i18next translation namespace data. |
| public/locales/zh-TW/common.json | Adds Traditional Chinese i18next common namespace data. |
| package.json | Updates scripts/deps; introduces shadcn-helper install hook and dependency upgrades. |
| lib/pagination.ts | Adds shared page parsing + visible page calculation helpers. |
| lib/activities.ts | Adds fetchActivitiesCatalog() to centralize activities fetch behavior. |
| i18n/zh-TW.ts | Adds SSR translation map for Traditional Chinese. |
| i18n/zh-CN.ts | Adds SSR translation map for Simplified Chinese. |
| i18n/en-US.ts | Adds SSR translation map for English (US). |
| i18n/index.ts | Adds SSR i18n store + cookie/header/query language loading. |
| i18n/config.ts | Extends client i18next config to support zh-TW and formatting tweaks. |
| eslint.config.ts | Adds React version setting and keeps existing TS-eslint rule override. |
| components/index.ini | Declares a shadcn registry component to be installed (pager). |
| components.json | Adds a custom shadcn registry URL. |
| app/tutoring/page.tsx | Converts tutoring list to SSR with GET search + URL tag selection + pagination. |
| app/tutoring/[slug]/page.tsx | Adds SSR i18n text usage in tutoring detail page. |
| app/restaurants/page.tsx | Converts restaurants list to SSR with GET search + URL filter + pagination. |
| app/restaurants/[id]/page.tsx | Adds SSR i18n text usage in restaurant detail page. |
| app/api/data/route.ts | Uses fetchActivitiesCatalog() for consistent upstream fetch behavior. |
| app/activities/page.tsx | Converts activities list to SSR with GET search + pagination and SSR locale loading. |
| app/activities/[id]/page.tsx | Uses fetchActivitiesCatalog() + SSR i18n text usage in activity detail page. |
| .gitignore | Ignores components/ui/ (shadcn UI components). |
Comments suppressed due to low confidence (1)
app/api/data/route.ts:14
fetchActivitiesCatalog()now throws on upstream fetch errors, but the API route catches everything and returns HTTP 500. Previously, upstream fetch failures returned 502 (bad gateway), which more accurately signals an upstream dependency issue. Consider preserving 502 for fetch failures so monitoring/clients can distinguish upstream vs server errors.
export async function GET() {
try {
const externalData = await fetchActivitiesCatalog();
const data = externalData.map(transformItem);
return NextResponse.json(data);
} catch (err) {
console.error('Failed to fetch data from external API:', err);
return NextResponse.json({ error: 'Failed to load data' }, { status: 500 });
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function parsePage(rawPage?: string): number { | ||
| const parsed = Number.parseInt(rawPage ?? '1', 10); | ||
| return Number.isFinite(parsed) && parsed > 0 ? parsed : 1; | ||
| } |
已在 |
Co-authored-by: South Drifter <shiy2008@gmail.com>
[optimize] upgrade to amondnet/vercel-action 42
|
@copilot 把所有客户端渲染的页面尽可能改成服务端渲染,实在有困难的就先不改。 |
已在 |
| interface AggregationProps { | ||
| searchLabel: string; | ||
| searchPlaceholder: string; | ||
| searchAriaLabel: string; | ||
| } |
| export const Aggregation = ({ | ||
| searchLabel, | ||
| searchPlaceholder, | ||
| searchAriaLabel, | ||
| }: AggregationProps) => { | ||
| return ( |
There was a problem hiding this comment.
| export const Aggregation = ({ | |
| searchLabel, | |
| searchPlaceholder, | |
| searchAriaLabel, | |
| }: AggregationProps) => { | |
| return ( | |
| export const Aggregation: FC<AggregationProps> = ({ | |
| searchLabel, | |
| searchPlaceholder, | |
| searchAriaLabel, | |
| }) => ( |
| <Select.Content className="rounded-lg border border-slate-200 bg-white shadow-lg z-50"> | ||
| <Select.Viewport className="p-1"> | ||
| {Object.entries(supportedLngDisplayNames).map(([lng, label]) => ( | ||
| {Object.entries(LanguageName).map(([lng, label]) => ( |
There was a problem hiding this comment.
| {Object.entries(LanguageName).map(([lng, label]) => ( | |
| {Object.entries(LanguageName).map(([language, label]) => ( |
已在 |
This PR addresses SEO for the three list surfaces by moving rendering/filtering state into URL-driven server pages. It also standardizes pagination to URL parameters with a 10-item page size and visible page links at the end of each list, fully unifies translation handling on
mobx-i18n, and converts the remaining feasible app page entrypoints from client rendering to server rendering.Scope (issue requirements)
GETform submissions.Server-rendered list pages
app/activities/page.tsxapp/restaurants/page.tsxapp/tutoring/page.tsxsearchParamson the server, compute filtered datasets, and slice results by page.mobx-i18n, including async fallback loading.Additional server-rendered app pages
app/home/page.tsxapp/origin/page.tsxapp/recommend/page.tsx/homeand/originnow resolve translations on the server instead of keeping the page entrypoints client-only./recommendnow readskeywordsfrom serversearchParams, loads recommendation data on the server, and renders recommendation results without a client-page bootstrap.URL-driven search + filtering
GETforms:/activities?keywords=.../restaurants?keywords=...&filter=.../tutoring?keywords=...&tag=.../recommend?keywords=...Pagination behavior
lib/pagination.ts.components/ui/mobx-restful-shadcn/pager/, while keeping the external component source itself out of Git.PAGE_SIZE = 10pageIndexquery paramData access alignment
fetchActivitiesCatalog()inlib/activities.tsand reused it in activities list/detail,/api/data, and/recommendto keep fetch behavior consistent.Client i18n unification
i18next/react-i18nextclient integration withmobx-i18ncontext-based injection.zh-CNas the default language map.Human changes