multica/apps/web/features/landing/components/landing-hero.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

110 lines
3.3 KiB
TypeScript

"use client";
import Image from "next/image";
import Link from "next/link";
import { useAuthStore } from "@/features/auth";
import { useLocale } from "../i18n";
import {
ClaudeCodeLogo,
CodexLogo,
GitHubMark,
githubUrl,
heroButtonClassName,
} from "./shared";
export function LandingHero() {
const { t } = useLocale();
const user = useAuthStore((s) => s.user);
return (
<div className="relative min-h-full overflow-hidden bg-[#05070b] text-white">
<LandingBackdrop />
<main className="relative z-10">
<section
id="product"
className="mx-auto max-w-[1320px] px-4 pb-16 pt-28 sm:px-6 sm:pt-32 lg:px-8 lg:pb-24 lg:pt-36"
>
<div className="mx-auto max-w-[1120px] text-center">
<h1 className="font-[family-name:var(--font-serif)] text-[3.65rem] leading-[0.93] tracking-[-0.038em] text-white drop-shadow-[0_10px_34px_rgba(0,0,0,0.32)] sm:text-[4.85rem] lg:text-[6.4rem]">
{t.hero.headlineLine1}
<br />
{t.hero.headlineLine2}
</h1>
<p className="mx-auto mt-7 max-w-[820px] text-[15px] leading-7 text-white/84 sm:text-[17px]">
{t.hero.subheading}
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link href={user ? "/issues" : "/login"} className={heroButtonClassName("solid")}>
{user ? t.header.dashboard : t.hero.cta}
</Link>
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className={heroButtonClassName("ghost")}
>
<GitHubMark className="size-4" />
GitHub
</Link>
</div>
</div>
<div className="mt-10 flex items-center justify-center gap-8">
<span className="text-[15px] text-white/50">
{t.hero.worksWith}
</span>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2.5 text-white/80">
<ClaudeCodeLogo className="size-5" />
<span className="text-[15px] font-medium">Claude Code</span>
</div>
<div className="flex items-center gap-2.5 text-white/80">
<CodexLogo className="size-5" />
<span className="text-[15px] font-medium">Codex</span>
</div>
</div>
</div>
<div id="preview" className="mt-10 sm:mt-12">
<ProductImage alt={t.hero.imageAlt} />
</div>
</section>
</main>
</div>
);
}
function LandingBackdrop() {
return (
<div className="pointer-events-none absolute inset-0">
<Image
src="/images/landing-bg.jpg"
alt=""
fill
priority
className="object-cover object-center"
/>
</div>
);
}
function ProductImage({ alt }: { alt: string }) {
return (
<div>
<div className="relative overflow-hidden border border-white/14">
<Image
src="/images/landing-hero.png"
alt={alt}
width={3532}
height={2382}
className="block h-auto w-full"
sizes="(max-width: 1320px) 100vw, 1320px"
quality={85}
/>
</div>
</div>
);
}