Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62350492e9 | ||
|
|
9540c2b550 | ||
|
|
006f3d4dbb | ||
|
|
ff8d497f97 | ||
|
|
9c2799f7d5 | ||
|
|
dd70f9d886 | ||
|
|
35cccf6a8f | ||
| c5616600fa | |||
|
|
986708fbb4 | ||
|
|
6a1b68ed2c | ||
|
|
32970acf32 | ||
|
|
812bb8c248 | ||
|
|
09f9e6588f | ||
|
|
10a167febd | ||
|
|
91c0686a46 | ||
|
|
40bfde8e57 | ||
|
|
b4ba7a2219 | ||
|
|
25e42e3af5 | ||
|
|
4398b53ea7 | ||
|
|
3e14bc652f | ||
|
|
aca7e11835 | ||
|
|
782de6e38a |
171
SEO-README.md
171
SEO-README.md
@@ -1,171 +0,0 @@
|
||||
# SEO 优化说明
|
||||
|
||||
本项目已完成基础 SEO 优化,以下是已实施的改动。
|
||||
|
||||
## 已完成的优化项
|
||||
|
||||
### 1. HTML Meta 标签优化 ✅
|
||||
|
||||
在 `index.html` 中添加了完整的 SEO 元数据:
|
||||
|
||||
- **基础 Meta 标签**
|
||||
- title, description, keywords
|
||||
- author, theme-color
|
||||
- canonical URL
|
||||
|
||||
- **Open Graph 标签**(社交媒体分享优化)
|
||||
- og:type, og:url, og:title
|
||||
- og:description, og:image, og:site_name
|
||||
|
||||
- **Twitter Card 标签**
|
||||
- twitter:card, twitter:title
|
||||
- twitter:description, twitter:image
|
||||
|
||||
- **结构化数据**(JSON-LD)
|
||||
- Schema.org WebSite 类型标记
|
||||
- 提升搜索引擎理解能力
|
||||
|
||||
### 2. SEO 配置文件 ✅
|
||||
|
||||
- **`public/robots.txt`** - 搜索引擎爬虫规则
|
||||
- 允许所有爬虫访问
|
||||
- 指定 sitemap 位置
|
||||
|
||||
- **`public/sitemap.xml`** - 站点地图(自动生成)
|
||||
- 包含所有工具页面 URL
|
||||
- 设置更新频率和优先级
|
||||
- 通过 `scripts/generate-sitemap.ts` 自动生成
|
||||
|
||||
- **`public/manifest.json`** - PWA 配置
|
||||
- 支持渐进式 Web 应用
|
||||
- 改善移动端体验
|
||||
|
||||
### 3. 构建流程优化 ✅
|
||||
|
||||
- **`package.json`**
|
||||
- 构建时自动生成 sitemap
|
||||
- 添加 tsx 依赖用于运行生成脚本
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 新增文件
|
||||
```
|
||||
public/
|
||||
├── robots.txt # 爬虫规则
|
||||
├── sitemap.xml # 站点地图(自动生成)
|
||||
└── manifest.json # PWA 配置
|
||||
|
||||
scripts/
|
||||
└── generate-sitemap.ts # Sitemap 生成脚本
|
||||
```
|
||||
|
||||
### 修改文件
|
||||
```
|
||||
index.html # 添加 SEO meta 标签和结构化数据
|
||||
package.json # 添加 sitemap 生成命令
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 开发
|
||||
```bash
|
||||
pnpm install # 安装依赖
|
||||
pnpm dev # 启动开发服务器
|
||||
```
|
||||
|
||||
### 构建
|
||||
```bash
|
||||
pnpm run build # 构建项目(自动生成 sitemap)
|
||||
pnpm run generate:sitemap # 单独生成 sitemap
|
||||
```
|
||||
|
||||
### 部署前检查
|
||||
|
||||
⚠️ **重要:部署前必须更新配置文件中的域名!**
|
||||
|
||||
需要将以下文件中的 `https://litek.typist.cc` 替换为你的实际域名:
|
||||
|
||||
1. **`index.html`**
|
||||
- canonical URL
|
||||
- Open Graph URL 和 image
|
||||
- Twitter Card image
|
||||
- Structured Data URL
|
||||
|
||||
2. **`public/robots.txt`**
|
||||
- Sitemap URL
|
||||
|
||||
3. **`scripts/generate-sitemap.ts`**
|
||||
- BASE_URL 常量
|
||||
|
||||
更新后重新构建:
|
||||
```bash
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
## SEO 验证
|
||||
|
||||
### 部署后验证
|
||||
|
||||
1. **提交 sitemap 到 Google Search Console**
|
||||
- 访问 https://search.google.com/search-console
|
||||
- 添加网站资源
|
||||
- 提交 sitemap: `https://你的域名/sitemap.xml`
|
||||
|
||||
2. **测试结构化数据**
|
||||
- 访问 https://validator.schema.org/
|
||||
- 输入网站 URL
|
||||
- 检查是否有错误
|
||||
|
||||
3. **测试 Open Graph 预览**
|
||||
- Facebook: https://developers.facebook.com/tools/debug/
|
||||
- Twitter: https://cards-dev.twitter.com/validator
|
||||
|
||||
4. **性能测试**
|
||||
- Google PageSpeed Insights: https://pagespeed.web.dev/
|
||||
- 目标分数: SEO > 95
|
||||
|
||||
## 可访问的 URL
|
||||
|
||||
部署后,以下 URL 应该可以正常访问:
|
||||
|
||||
- `https://你的域名/` - 主页
|
||||
- `https://你的域名/robots.txt` - 爬虫规则
|
||||
- `https://你的域名/sitemap.xml` - 站点地图
|
||||
- `https://你的域名/manifest.json` - PWA 配置
|
||||
- `https://你的域名/tool/uuid` - UUID 工具
|
||||
- `https://你的域名/tool/json` - JSON 工具
|
||||
- `https://你的域名/tool/base64` - Base64 工具
|
||||
- `https://你的域名/tool/network/dns` - DNS 工具
|
||||
- ... 其他工具页面
|
||||
|
||||
## 添加新工具时更新 SEO
|
||||
|
||||
当添加新工具时,需要更新 `scripts/generate-sitemap.ts`:
|
||||
|
||||
```typescript
|
||||
const tools = [
|
||||
// 现有工具...
|
||||
{ path: '/tool/你的新工具', priority: '0.9' },
|
||||
];
|
||||
```
|
||||
|
||||
然后重新构建项目。
|
||||
|
||||
## SEO 效果预期
|
||||
|
||||
- **1-2 周**:搜索引擎开始索引页面
|
||||
- **1-2 月**:主要关键词开始有排名
|
||||
- **3-6 月**:稳定的搜索排名和自然流量增长
|
||||
|
||||
## 技术栈
|
||||
|
||||
- React 19 + TypeScript
|
||||
- Vite (Rolldown)
|
||||
- React Router v7
|
||||
- Radix UI + Tailwind CSS 4
|
||||
- Nginx
|
||||
|
||||
## 联系方式
|
||||
|
||||
需要更多工具或有建议?联系:litek@mail.typist.cc
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
<link rel="dns-prefetch" href="https://ipinfo.io">
|
||||
<link rel="preconnect" href="https://ipinfo.io" crossorigin>
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Noto+Sans+SC:wght@400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://litek.typist.cc/" />
|
||||
@@ -59,5 +64,9 @@
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
<!-- Cloudflare Web Analytics -->
|
||||
<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "2aecdc025eb043bc89ce931b54a80054"}'></script>
|
||||
<!-- End Cloudflare Web Analytics -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "litek",
|
||||
"private": true,
|
||||
"version": "0.0.19",
|
||||
"version": "0.0.29",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -44,11 +44,14 @@
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"globals": "^16.4.0",
|
||||
"terser": "^5.44.0",
|
||||
"tsx": "^4.19.2",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.45.0",
|
||||
"vite": "npm:rolldown-vite@7.1.14"
|
||||
"vite": "npm:rolldown-vite@7.1.14",
|
||||
"vite-plugin-pwa": "^1.1.0",
|
||||
"workbox-window": "^7.3.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
|
||||
2811
pnpm-lock.yaml
generated
2811
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -114,11 +114,7 @@ export const AppSidebar = () => {
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter className="flex flex-row justify-between items-center gap-2">
|
||||
<SidebarMenuButton variant="outline" asChild>
|
||||
<a href="mailto:litek@mail.typist.cc">need more tools?</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarFooter>
|
||||
<SidebarFooter className="flex flex-col gap-2" />
|
||||
</Sidebar>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +1,94 @@
|
||||
import { type FC } from "react";
|
||||
|
||||
import { type FC, useState } from "react";
|
||||
import { RefreshCw, Copy } from "lucide-react";
|
||||
import * as uuid from 'uuid'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface IDGeneratorProps {
|
||||
label: string;
|
||||
value: string;
|
||||
onRegenerate: () => void;
|
||||
}
|
||||
|
||||
const IDGenerator: FC<IDGeneratorProps> = ({ label, value, onRegenerate }) => {
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
toast(`${label} has been copied to clipboard`);
|
||||
} catch (err) {
|
||||
toast.error("Copy failed");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="font-medium">{label}</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex-1 px-3 py-2 bg-muted rounded-md font-mono text-sm break-all max-w-[400px]">
|
||||
{value}
|
||||
</span>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
onClick={onRegenerate}
|
||||
title="Regenerate"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
onClick={copyToClipboard}
|
||||
title="Copy"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Tool: FC = () => {
|
||||
const [uuidV1, setUuidV1] = useState(() => uuid.v1());
|
||||
const [uuidV4, setUuidV4] = useState(() => uuid.v4());
|
||||
const [uuidV6, setUuidV6] = useState(() => uuid.v6());
|
||||
const [uuidV7, setUuidV7] = useState(() => uuid.v7());
|
||||
const [nanoId, setNanoId] = useState(() => nanoid());
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-sm text-muted-foreground">Refresh the page to generate new UUID</span>
|
||||
<label>UUID Version 1</label>
|
||||
<span>{uuid.v1()}</span>
|
||||
<label>UUID Version 4</label>
|
||||
<span>{uuid.v4()}</span>
|
||||
<label>UUID Version 6</label>
|
||||
<span>{uuid.v6()}</span>
|
||||
<label>UUID Version 7</label>
|
||||
<span>{uuid.v7()}</span>
|
||||
<label>Nano ID</label>
|
||||
<span>{nanoid()}</span>
|
||||
<span className="text-sm text-muted-foreground">Click the refresh button to regenerate the corresponding ID</span>
|
||||
|
||||
<IDGenerator
|
||||
label="UUID Version 1"
|
||||
value={uuidV1}
|
||||
onRegenerate={() => setUuidV1(uuid.v1())}
|
||||
/>
|
||||
|
||||
<IDGenerator
|
||||
label="UUID Version 4"
|
||||
value={uuidV4}
|
||||
onRegenerate={() => setUuidV4(uuid.v4())}
|
||||
/>
|
||||
|
||||
<IDGenerator
|
||||
label="UUID Version 6"
|
||||
value={uuidV6}
|
||||
onRegenerate={() => setUuidV6(uuid.v6())}
|
||||
/>
|
||||
|
||||
<IDGenerator
|
||||
label="UUID Version 7"
|
||||
value={uuidV7}
|
||||
onRegenerate={() => setUuidV7(uuid.v7())}
|
||||
/>
|
||||
|
||||
<IDGenerator
|
||||
label="Nano ID"
|
||||
value={nanoId}
|
||||
onRegenerate={() => setNanoId(nanoid())}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-family: 'Roboto Mono', 'Noto Sans SC', 'SF Mono', Consolas, monospace;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
41
src/main.tsx
41
src/main.tsx
@@ -2,7 +2,7 @@ import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import './index.css'
|
||||
import { AppRouter } from './router'
|
||||
@@ -13,3 +13,42 @@ createRoot(document.getElementById('root')!).render(
|
||||
<Toaster />
|
||||
</StrictMode>
|
||||
)
|
||||
|
||||
// 注册 Service Worker
|
||||
if ('serviceWorker' in navigator && import.meta.env.PROD) {
|
||||
import('workbox-window').then(({ Workbox }) => {
|
||||
const wb = new Workbox('/sw.js')
|
||||
|
||||
// 检测到新版本时,在后台下载完成后显示通知
|
||||
wb.addEventListener('waiting', () => {
|
||||
// 显示更新通知,右上角弹窗
|
||||
toast.info('found new version', {
|
||||
description: 'new content available, click update to get the latest version',
|
||||
duration: Infinity, // 持续显示,直到用户操作
|
||||
action: {
|
||||
label: 'update now',
|
||||
onClick: () => {
|
||||
// 用户点击更新按钮
|
||||
wb.messageSkipWaiting()
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
label: 'later',
|
||||
onClick: () => {
|
||||
// 用户选择稍后更新,关闭通知
|
||||
// 新版本会在下次手动刷新时自动激活
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 当新的 Service Worker 接管页面时,刷新页面
|
||||
wb.addEventListener('controlling', () => {
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
wb.register()
|
||||
}).catch((error) => {
|
||||
console.error('Failed to register service worker:', error)
|
||||
})
|
||||
}
|
||||
|
||||
3
src/vite-env.d.ts
vendored
Normal file
3
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
|
||||
108
vite.config.ts
108
vite.config.ts
@@ -2,10 +2,85 @@ import path from "path"
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from "@tailwindcss/vite"
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
includeAssets: ['lite.svg', 'robots.txt', 'sitemap.xml'],
|
||||
manifest: {
|
||||
name: 'Lite Kit - Lightweight Online Tools',
|
||||
short_name: 'Lite Kit',
|
||||
description: 'Free online tools including UUID generator, JSON formatter, Base64 encoder/decoder, network testing tools and more',
|
||||
theme_color: '#000000',
|
||||
background_color: '#000000',
|
||||
display: 'standalone',
|
||||
icons: [
|
||||
{
|
||||
src: '/lite.svg',
|
||||
type: 'image/svg+xml',
|
||||
sizes: 'any'
|
||||
}
|
||||
]
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,svg,png,ico,woff2}'],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/ipinfo\.io\/.*/i,
|
||||
handler: "NetworkFirst",
|
||||
options: {
|
||||
cacheName: 'ipinfo-cache',
|
||||
expiration: {
|
||||
maxEntries: 10,
|
||||
maxAgeSeconds: 60 * 60 // 延长到 1 小时
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Google Fonts 样式表缓存
|
||||
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
||||
handler: 'StaleWhileRevalidate',
|
||||
options: {
|
||||
cacheName: 'google-fonts-stylesheets',
|
||||
expiration: {
|
||||
maxEntries: 10,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 365 // 1 年
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Google Fonts 字体文件缓存
|
||||
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'google-fonts-webfonts',
|
||||
expiration: {
|
||||
maxEntries: 30,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 365 // 1 年
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
cleanupOutdatedCaches: true,
|
||||
clientsClaim: true, // 新 SW 激活后立即接管
|
||||
skipWaiting: false // 不自动跳过等待,需要手动触发
|
||||
}
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
@@ -15,11 +90,15 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: (id) => {
|
||||
// React核心库
|
||||
if (id.includes('node_modules/react') ||
|
||||
id.includes('node_modules/react-dom') ||
|
||||
id.includes('node_modules/react-router-dom')) {
|
||||
return 'react-vendor';
|
||||
// React 核心拆分得更细
|
||||
if (id.includes('node_modules/react/') && !id.includes('node_modules/react-dom')) {
|
||||
return 'react-core';
|
||||
}
|
||||
if (id.includes('node_modules/react-dom/')) {
|
||||
return 'react-dom';
|
||||
}
|
||||
if (id.includes('node_modules/react-router-dom')) {
|
||||
return 'react-router';
|
||||
}
|
||||
// Radix UI组件
|
||||
if (id.includes('node_modules/@radix-ui')) {
|
||||
@@ -29,9 +108,26 @@ export default defineConfig({
|
||||
if (id.includes('node_modules/lucide-react')) {
|
||||
return 'icons';
|
||||
}
|
||||
// 其他工具库
|
||||
if (id.includes('node_modules/')) {
|
||||
return 'vendor';
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
// 启用更激进的压缩
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
pure_funcs: ['console.log'],
|
||||
// 移除未使用的代码
|
||||
unused: true,
|
||||
// 移除死代码
|
||||
dead_code: true,
|
||||
},
|
||||
},
|
||||
chunkSizeWarningLimit: 500,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user