multica/apps/web/app/(landing)/layout.tsx
Naiyuan Qing 3bd504fe5f feat(web): SEO optimization and auth flow improvements for landing pages
- Split landing pages into Server/Client Components to enable Next.js metadata exports
- Add robots.ts, sitemap.ts, JSON-LD structured data, OpenGraph and viewport config
- Fix i18n hydration mismatch: detect locale server-side via cookie/Accept-Language header
- Replace localStorage with cookie for locale persistence (SSR-readable)
- Dynamic <html lang> based on locale cookie
- Optimize images with next/image (avif/webp formats, quality config)
- Add /homepage route as always-visible landing page (no auth redirect)
- Auth-aware CTA buttons: show "Dashboard"/"进入工作台" for logged-in users
- Login page redirects authenticated users to dashboard
- Unify logout/401 redirect to "/" instead of "/login"
- Fix title template to avoid double "Multica" suffix on homepage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:54:11 +08:00

75 lines
2 KiB
TypeScript

import { cookies, headers } from "next/headers";
import { Instrument_Serif, Noto_Serif_SC } from "next/font/google";
import { LocaleProvider } from "@/features/landing/i18n";
import type { Locale } from "@/features/landing/i18n";
const instrumentSerif = Instrument_Serif({
subsets: ["latin"],
weight: "400",
variable: "--font-serif",
});
const notoSerifSC = Noto_Serif_SC({
subsets: ["latin"],
weight: "400",
variable: "--font-serif-zh",
});
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
name: "Multica",
url: "https://www.multica.ai",
sameAs: ["https://github.com/multica-ai/multica"],
},
{
"@type": "SoftwareApplication",
name: "Multica",
applicationCategory: "ProjectManagement",
operatingSystem: "Web",
description:
"AI-native task management platform that turns coding agents into real teammates.",
offers: {
"@type": "Offer",
price: "0",
priceCurrency: "USD",
},
},
],
};
async function getInitialLocale(): Promise<Locale> {
// 1. User's explicit preference (cookie set when they switch language)
const cookieStore = await cookies();
const stored = cookieStore.get("multica-locale")?.value;
if (stored === "en" || stored === "zh") return stored;
// 2. Detect from Accept-Language header
const headersList = await headers();
const acceptLang = headersList.get("accept-language") ?? "";
if (acceptLang.includes("zh")) return "zh";
return "en";
}
export default async function LandingLayout({
children,
}: {
children: React.ReactNode;
}) {
const initialLocale = await getInitialLocale();
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<div className={`${instrumentSerif.variable} ${notoSerifSC.variable} h-full overflow-x-hidden overflow-y-auto bg-white`}>
<LocaleProvider initialLocale={initialLocale}>{children}</LocaleProvider>
</div>
</>
);
}