feat: 服务器迁移后的全量演进(依赖升级/安全审计/设计统一/截图/SEO 底座化)

在原仓库历史之上同步本服务器上的全部演进:

- 迁移至新服务器并重建 PG 数据库 + ddnc 开发域名绑定
- 全部依赖升级至最新(Next 16.3.5 / React 19.3 / better-auth 1.7.5 / Drizzle 0.45 / TS 7),
  overrides 消除已知 CVE,安全审计 19 项发现全部修复闭环
- 设计统一:各站宽度 token(--container + site-container)、classic hero 重设计、
  头部换行修复、页面过渡与 View Transitions
- 截图功能:api.adx9.com 截图服务接入(ow=800 本地化 webp、cf=1、去重/限流/占位图)
- SEO 底座化:canonical/hreflang/sitemap/JSON-LD/robots 全部收进 core,新站自动继承
- 加载体验:五站五形态路由骨架(data-theme 切换、shimmer 微光、延迟淡入)+
  过滤 useTransition 交叉淡入淡出(card-enter 错峰入场)
- 构建:experimental.cpus 限流防 PG 连接打满;清理死组件链
This commit is contained in:
bigcode
2026-09-15 18:19:53 +08:00
parent 0eed5183b2
commit 05440ffb5b
144 changed files with 3055 additions and 12937 deletions
@@ -1,2 +1,2 @@
// 自动生成:[locale]/(admin)/layout.tsx 的薄路由
export { default } from "@aidir/core/pages/[locale]/(admin)/layout";
export { default, metadata } from "@aidir/core/pages/[locale]/(admin)/layout";
@@ -1,2 +1,2 @@
// 自动生成:[locale]/(auth)/layout.tsx 的薄路由
export { default } from "@aidir/core/pages/[locale]/(auth)/layout";
export { default, metadata } from "@aidir/core/pages/[locale]/(auth)/layout";
@@ -1,2 +1,2 @@
// 自动生成:[locale]/(protected)/layout.tsx 的薄路由
export { default } from "@aidir/core/pages/[locale]/(protected)/layout";
export { default, metadata } from "@aidir/core/pages/[locale]/(protected)/layout";
+1 -1
View File
@@ -1,2 +1,2 @@
// 自动生成:[locale]/layout.tsx 的薄路由
export { default, generateStaticParams } from "@aidir/core/pages/[locale]/layout";
export { default, generateMetadata, generateStaticParams } from "@aidir/core/pages/[locale]/layout";
@@ -0,0 +1,2 @@
// 自动生成:api/screenshots/[slug]/route.ts 的薄路由
export { GET } from "@aidir/core/pages/api/screenshots/[slug]/route";
+2
View File
@@ -0,0 +1,2 @@
// 自动生成:robots.ts 的薄路由
export { default } from "@aidir/core/pages/robots";
+36 -41
View File
@@ -1,6 +1,6 @@
// 自动生成:站点首页组件(源自底座 classic 形态,品牌来自 @/site/brand)
import { siteConfig } from "@/site/brand";
import { Search, TrendingUp, Sparkles, Clock, Award, LayoutGrid, MessageSquareQuote } from "lucide-react";
import { Search, ArrowRight } from "lucide-react";
import { getTranslations } from "next-intl/server";
import { ToolCard, ToolCardGrid } from "@/components/tool-card";
import { LocaleLink } from "@/components/locale-link";
@@ -57,13 +57,13 @@ async function SiteHome({ locale }: { locale: string }) {
return (
<div className="bg-background">
{/* Hero */}
<section className="hero-dark border-b border-border">
<div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 md:py-24">
<h1 className="mx-auto max-w-3xl text-3xl font-bold tracking-tight text-foreground sm:text-4xl md:text-5xl">
{/* Hero:亮色排版优先,搜索框是唯一焦点 */}
<section className="border-b border-border">
<div className="site-container px-4 pb-14 pt-16 text-center sm:px-6 md:pb-16 md:pt-24">
<h1 className="mx-auto max-w-3xl text-balance text-3xl font-bold tracking-tight text-foreground sm:text-4xl md:text-5xl">
{th("title")}
</h1>
<p className="mx-auto mt-4 max-w-2xl text-sm text-muted-foreground sm:text-base">
<p className="mx-auto mt-4 max-w-2xl text-sm leading-relaxed text-muted-foreground sm:text-base">
{th("description")}
</p>
<form action={`/${locale}/tools`} className="mx-auto mt-8 max-w-xl">
@@ -72,39 +72,39 @@ async function SiteHome({ locale }: { locale: string }) {
<input
name="search"
placeholder={t("searchPlaceholder")}
className="h-12 w-full rounded-full border border-border bg-background pl-11 pr-28 text-sm text-foreground shadow-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
className="h-12 w-full rounded-lg border border-border bg-background pl-11 pr-28 text-sm text-foreground shadow-sm placeholder:text-muted-foreground focus:border-primary/50 focus:outline-none focus:ring-4 focus:ring-primary/10"
aria-label="Search AI tools"
/>
<button
type="submit"
className="absolute right-1.5 top-1.5 h-9 rounded-full bg-primary px-5 text-xs font-semibold text-primary-foreground transition-opacity hover:opacity-90"
className="absolute right-1.5 top-1.5 h-9 rounded-md bg-primary px-5 text-xs font-semibold text-primary-foreground transition-opacity hover:opacity-90"
>
{t("searchButton")}
</button>
</div>
</form>
{/* 统计条(深色 Hero 内的白色数字) */}
<div className="mx-auto mt-10 flex max-w-lg items-center justify-center gap-10 border-t border-white/15 pt-6">
{/* 统计条:同行文字,数字用衬线级对比,不做卡片 */}
<dl className="mx-auto mt-10 flex max-w-lg items-start justify-center gap-12">
{[
{ v: "1,000+", l: "AI tools" },
{ v: "14", l: "Categories" },
{ v: "7", l: "Rankings" },
].map((s) => (
<div key={s.l} className="text-center">
<div className="text-2xl font-bold text-white">{s.v}</div>
<div className="text-xs text-white/60">{s.l}</div>
<dd className="text-xl font-semibold text-foreground tabular-nums">{s.v}</dd>
<dt className="mt-0.5 text-xs text-muted-foreground">{s.l}</dt>
</div>
))}
</div>
</dl>
<div className="mt-8 flex flex-wrap items-center justify-center gap-2 text-xs">
<span className="text-white/60">{t("popularLabel")}</span>
<div className="mt-7 flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-xs">
<span className="text-muted-foreground">{t("popularLabel")}</span>
{t.raw("popularSearches").map((q: string) => (
<LocaleLink
key={q}
href={`/tools?search=${q}`}
className="rounded-full bg-white/10 px-2.5 py-1 text-white/80 transition-colors hover:bg-white/20"
className="text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline"
>
{q}
</LocaleLink>
@@ -113,7 +113,7 @@ async function SiteHome({ locale }: { locale: string }) {
</div>
</section>
<div className="mx-auto max-w-7xl space-y-12 px-4 py-12 sm:px-6">
<div className="site-container space-y-12 px-4 py-12 sm:px-6">
{/* Trending */}
{data.trending.length > 0 && (
<section>
@@ -140,10 +140,9 @@ async function SiteHome({ locale }: { locale: string }) {
<LocaleLink
key={category.id}
href={`/category/${category.slug}`}
className="group rounded-xl border border-border p-4 transition-all hover:border-primary/30 hover:shadow-sm"
className="group border-b border-border py-3 transition-colors hover:border-primary/40"
>
<LayoutGrid className="h-4 w-4 text-primary" />
<h3 className="mt-2 truncate text-sm font-medium text-foreground group-hover:text-primary">
<h3 className="text-sm font-medium text-foreground group-hover:text-primary">
{locale === "zh" && category.nameZh
? category.nameZh
: category.nameEn}
@@ -193,7 +192,7 @@ async function SiteHome({ locale }: { locale: string }) {
<LocaleLink
key={uc.id}
href={`/ai-tools-for/${uc.slug}`}
className="rounded-full border border-border px-3.5 py-1.5 text-xs text-foreground transition-colors hover:border-primary/40 hover:bg-primary/5"
className="border-b border-border pb-1 text-xs text-foreground transition-colors hover:border-primary hover:text-primary"
>
{locale === "zh" && uc.nameZh ? uc.nameZh : uc.nameEn}
<span className="ml-1.5 text-muted-foreground tabular-nums">
@@ -219,13 +218,12 @@ async function SiteHome({ locale }: { locale: string }) {
<LocaleLink
key={col.id}
href={`/best/${col.slug}`}
className="group rounded-xl border border-border p-5 transition-all hover:border-primary/30 hover:shadow-sm"
className="group border-t border-border pt-4"
>
<Award className="h-4 w-4 text-primary" />
<h3 className="mt-2 text-sm font-semibold text-foreground group-hover:text-primary">
<h3 className="text-sm font-semibold text-foreground group-hover:text-primary">
{locale === "zh" && col.nameZh ? col.nameZh : col.nameEn}
</h3>
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
<p className="mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
{locale === "zh" && col.introZh ? col.introZh : col.introEn}
</p>
<p className="mt-2 text-xs text-muted-foreground tabular-nums">
@@ -241,26 +239,25 @@ async function SiteHome({ locale }: { locale: string }) {
{data.latestReviews.length > 0 && (
<section>
<SectionHeader title={t("reviewsTitle")} />
<div className="grid gap-3 md:grid-cols-2">
<div className="divide-y divide-border border-y border-border">
{data.latestReviews.map((review, i) => (
<LocaleLink
key={i}
href={`/tools/${review.toolSlug}#reviews`}
className="rounded-xl border border-border p-4 transition-colors hover:border-primary/30"
className="group block py-3.5"
>
<div className="flex items-center gap-2">
<MessageSquareQuote className="h-3.5 w-3.5 text-primary" />
<span className="text-xs font-medium text-foreground">
<div className="flex items-baseline gap-2 text-xs">
<span className="font-medium text-foreground">
{review.authorName}
</span>
<span className="text-xs text-muted-foreground">
<span className="text-muted-foreground">
on {review.toolName}
</span>
<span className="ml-auto text-xs font-semibold text-amber-500 tabular-nums">
<span className="ml-auto font-semibold text-foreground tabular-nums">
{review.rating}/5
</span>
</div>
<p className="mt-2 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
<p className="mt-1.5 line-clamp-2 text-xs leading-relaxed text-muted-foreground group-hover:text-foreground">
{review.content}
</p>
</LocaleLink>
@@ -269,27 +266,25 @@ async function SiteHome({ locale }: { locale: string }) {
</section>
)}
{/* Submit CTA */}
<section className="rounded-2xl border border-border bg-secondary/30 p-8 text-center">
<Sparkles className="mx-auto h-5 w-5 text-primary" />
<h2 className="mt-3 text-lg font-semibold text-foreground">
{/* Submit CTA:排版式收尾,不做卡片 */}
<section className="border-t border-border pt-10 text-center">
<h2 className="text-lg font-semibold text-foreground">
Built an AI tool?
</h2>
<p className="mx-auto mt-1 max-w-md text-sm text-muted-foreground">
<p className="mx-auto mt-1.5 max-w-md text-sm leading-relaxed text-muted-foreground">
Submit it to {siteConfig.brand.name} and get discovered by thousands of users
looking for tools like yours.
</p>
<LocaleLink
href="/submit"
className="mt-4 inline-flex items-center gap-1.5 rounded-full bg-primary px-5 py-2 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90"
className="mt-5 inline-flex items-center gap-1.5 rounded-md bg-primary px-5 py-2 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90"
>
<TrendingUp className="h-4 w-4" />
Submit your tool
<ArrowRight className="h-3.5 w-3.5" />
</LocaleLink>
</section>
</div>
</div>
);
}
export { SiteHome };