feat(website): SEO 升级 — BrowserRouter + 每页独立 meta
- HashRouter → BrowserRouter(basename /KimiSwitch),GitHub Pages 用 404.html SPA 回退,/features /download /changelog 变为可收录的独立 URL - 每页独立 title / description / OG / canonical(RouteMeta 随语言切换) - og:image 改为绝对地址;sitemap 更新为干净路径
This commit is contained in:
1 parent
5788624d12
commit
1b5cd0f416
8 files changed
+139
-21
No files matched your search
+2
-2
@@ -24,7 +24,7 @@
|
|||||||
content="一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek 等供应商,用量与账单一目了然,开源 MIT。"
|
content="一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek 等供应商,用量与账单一目了然,开源 MIT。"
|
||||||
/>
|
/>
|
||||||
<meta property="og:url" content="https://billowliu2.github.io/KimiSwitch/" />
|
<meta property="og:url" content="https://billowliu2.github.io/KimiSwitch/" />
|
||||||
<meta property="og:image" content="%BASE_URL%og.png" />
|
<meta property="og:image" content="https://billowliu2.github.io/KimiSwitch/og.png" />
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
name="twitter:description"
|
name="twitter:description"
|
||||||
content="一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek 等供应商,用量与账单一目了然,开源 MIT。"
|
content="一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek 等供应商,用量与账单一目了然,开源 MIT。"
|
||||||
/>
|
/>
|
||||||
<meta name="twitter:image" content="%BASE_URL%og.png" />
|
<meta name="twitter:image" content="https://billowliu2.github.io/KimiSwitch/og.png" />
|
||||||
|
|
||||||
<!-- Canonical -->
|
<!-- Canonical -->
|
||||||
<link rel="canonical" href="https://billowliu2.github.io/KimiSwitch/" />
|
<link rel="canonical" href="https://billowliu2.github.io/KimiSwitch/" />
|
||||||
|
|||||||
Generated
+18
@@ -16,6 +16,7 @@
|
|||||||
"react-router-dom": "^7.18.2"
|
"react-router-dom": "^7.18.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^26.1.2",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
@@ -1299,6 +1300,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "26.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz",
|
||||||
|
"integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~8.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
@@ -2723,6 +2734,13 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"react-router-dom": "^7.18.2"
|
"react-router-dom": "^7.18.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^26.1.2",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
|||||||
@@ -6,27 +6,17 @@
|
|||||||
<priority>1.0</priority>
|
<priority>1.0</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://billowliu2.github.io/KimiSwitch/#/features</loc>
|
<loc>https://billowliu2.github.io/KimiSwitch/features</loc>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://billowliu2.github.io/KimiSwitch/#/showcase</loc>
|
<loc>https://billowliu2.github.io/KimiSwitch/changelog</loc>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://billowliu2.github.io/KimiSwitch/#/performance</loc>
|
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.7</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://billowliu2.github.io/KimiSwitch/#/changelog</loc>
|
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://billowliu2.github.io/KimiSwitch/#/download</loc>
|
<loc>https://billowliu2.github.io/KimiSwitch/download</loc>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import Download from "./sections/Download";
|
|||||||
import Privacy from "./sections/Privacy";
|
import Privacy from "./sections/Privacy";
|
||||||
import CTA from "./sections/CTA";
|
import CTA from "./sections/CTA";
|
||||||
import Footer from "./components/Footer";
|
import Footer from "./components/Footer";
|
||||||
|
import { useLang, type Dict } from "./i18n";
|
||||||
|
|
||||||
|
const SITE_URL = "https://billowliu2.github.io/KimiSwitch";
|
||||||
|
type SeoKey = keyof Dict["seo"];
|
||||||
|
|
||||||
function ScrollToTop() {
|
function ScrollToTop() {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
@@ -17,6 +21,47 @@ function ScrollToTop() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Update per-page title / description / OG / canonical for SEO. */
|
||||||
|
function RouteMeta() {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const { t } = useLang();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const page = pathname.replace(/\/+$/, "");
|
||||||
|
const seoKey: SeoKey =
|
||||||
|
page === "/features" || page === "/changelog" || page === "/download"
|
||||||
|
? (page.slice(1) as SeoKey)
|
||||||
|
: "home";
|
||||||
|
const m = t.seo[seoKey];
|
||||||
|
const url = `${SITE_URL}${page === "" ? "/" : page}`;
|
||||||
|
|
||||||
|
document.title = m.title;
|
||||||
|
setMeta("name", "description", m.description);
|
||||||
|
setMeta("property", "og:title", m.title);
|
||||||
|
setMeta("property", "og:description", m.description);
|
||||||
|
setMeta("property", "og:url", url);
|
||||||
|
setMeta("name", "twitter:title", m.title);
|
||||||
|
setMeta("name", "twitter:description", m.description);
|
||||||
|
|
||||||
|
const canon = document.querySelector('link[rel="canonical"]');
|
||||||
|
if (canon) canon.setAttribute("href", url);
|
||||||
|
}, [pathname, t]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMeta(attr: "name" | "property", key: string, content: string) {
|
||||||
|
let el = document.head.querySelector<HTMLMetaElement>(
|
||||||
|
`meta[${attr}="${key}"]`,
|
||||||
|
);
|
||||||
|
if (!el) {
|
||||||
|
el = document.createElement("meta");
|
||||||
|
el.setAttribute(attr, key);
|
||||||
|
document.head.appendChild(el);
|
||||||
|
}
|
||||||
|
el.setAttribute("content", content);
|
||||||
|
}
|
||||||
|
|
||||||
const homePage = (
|
const homePage = (
|
||||||
<>
|
<>
|
||||||
<Hero />
|
<Hero />
|
||||||
@@ -30,6 +75,7 @@ export default function App() {
|
|||||||
<Nav />
|
<Nav />
|
||||||
<main className="flex-1">
|
<main className="flex-1">
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
|
<RouteMeta />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={homePage} />
|
<Route path="/" element={homePage} />
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -252,6 +252,28 @@ const zh = {
|
|||||||
download: "免费下载",
|
download: "免费下载",
|
||||||
source: "GitHub 源码",
|
source: "GitHub 源码",
|
||||||
},
|
},
|
||||||
|
seo: {
|
||||||
|
home: {
|
||||||
|
title: "Kimi Switch — 多 LLM 供应商统一管理 · 用量监控 · 账单一目了然",
|
||||||
|
description:
|
||||||
|
"Kimi Switch / KimiCodeSwitch — Windows 桌面端多 LLM 供应商统一管理器,一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek / OpenRouter / 基元律动,用量账单实时监控,开源 MIT。",
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
title: "核心功能 - Kimi Switch",
|
||||||
|
description:
|
||||||
|
"Kimi Switch 核心功能:多供应商管理、用量与账单监控、Kimi 一键授权登录、智能模型发现与自动更新,22 个预设开箱即用。",
|
||||||
|
},
|
||||||
|
changelog: {
|
||||||
|
title: "更新日志 - Kimi Switch",
|
||||||
|
description:
|
||||||
|
"Kimi Switch 更新日志:每个版本的重要变更记录,同步自 GitHub Releases。",
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
title: "下载 - Kimi Switch",
|
||||||
|
description:
|
||||||
|
"下载 Kimi Switch:Windows .msi、macOS .dmg、Linux .deb / .AppImage / .rpm,免费开源 MIT。",
|
||||||
|
},
|
||||||
|
},
|
||||||
footer: {
|
footer: {
|
||||||
recommend: "推荐供应商",
|
recommend: "推荐供应商",
|
||||||
mirror: "国内镜像",
|
mirror: "国内镜像",
|
||||||
@@ -516,6 +538,28 @@ const en: Dict = {
|
|||||||
download: "Free Download",
|
download: "Free Download",
|
||||||
source: "GitHub Source",
|
source: "GitHub Source",
|
||||||
},
|
},
|
||||||
|
seo: {
|
||||||
|
home: {
|
||||||
|
title: "Kimi Switch — Unified Management for AI Providers · Usage & Billing",
|
||||||
|
description:
|
||||||
|
"Kimi Switch / KimiCodeSwitch — a Windows desktop manager for multiple LLM providers. Switch between Kimi, Zhipu GLM, MiniMax, DeepSeek, OpenRouter and TokenRhythm in one click, with usage and billing at a glance. Open source MIT.",
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
title: "Core Features - Kimi Switch",
|
||||||
|
description:
|
||||||
|
"Kimi Switch core features: multi-provider management, usage & billing, one-click Kimi login, smart model discovery and auto updates. 22 presets out of the box.",
|
||||||
|
},
|
||||||
|
changelog: {
|
||||||
|
title: "Changelog - Kimi Switch",
|
||||||
|
description:
|
||||||
|
"Kimi Switch changelog: notable changes in every release, synced from GitHub Releases.",
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
title: "Download - Kimi Switch",
|
||||||
|
description:
|
||||||
|
"Download Kimi Switch: Windows .msi, macOS .dmg, Linux .deb / .AppImage / .rpm. Free and open source under MIT.",
|
||||||
|
},
|
||||||
|
},
|
||||||
footer: {
|
footer: {
|
||||||
recommend: "Recommended Providers",
|
recommend: "Recommended Providers",
|
||||||
mirror: "China mirror",
|
mirror: "China mirror",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { HashRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { LangProvider } from "./i18n";
|
import { LangProvider } from "./i18n";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<HashRouter>
|
<BrowserRouter basename="/KimiSwitch">
|
||||||
<LangProvider>
|
<LangProvider>
|
||||||
<App />
|
<App />
|
||||||
</LangProvider>
|
</LangProvider>
|
||||||
</HashRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
+22
-3
@@ -1,11 +1,30 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig, type Plugin } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { copyFileSync } from "node:fs";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GitHub Pages SPA fallback: any deep path (e.g. /KimiSwitch/features) is
|
||||||
|
* served by 404.html; duplicating the built index.html there lets the
|
||||||
|
* BrowserRouter boot and render the right route. This keeps every page
|
||||||
|
* indexable as a real URL instead of a `#/` fragment.
|
||||||
|
*/
|
||||||
|
function generate404(): Plugin {
|
||||||
|
return {
|
||||||
|
name: "generate-404",
|
||||||
|
apply: "build",
|
||||||
|
closeBundle() {
|
||||||
|
const dist = fileURLToPath(new URL("./dist", import.meta.url));
|
||||||
|
copyFileSync(`${dist}/index.html`, `${dist}/404.html`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// GitHub Pages subpath: billowliu2.github.io/KimiSwitch/
|
// GitHub Pages subpath: billowliu2.github.io/KimiSwitch/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: "/KimiSwitch/",
|
base: "/KimiSwitch/",
|
||||||
plugins: [react()],
|
plugins: [react(), generate404()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: { "@": "/src" },
|
alias: { "@": "/src" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user