20 Commits

Author SHA1 Message Date
typist
62350492e9 0.0.29
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m15s
2025-10-29 21:47:03 +08:00
typist
9540c2b550 feat: improve service worker update notifications
- Added toast notifications for users when a new service worker version is available.
- Updated service worker configuration to require manual activation for updates.
- Enhanced user experience by allowing users to choose when to update the application.
2025-10-29 21:46:54 +08:00
typist
006f3d4dbb 0.0.28
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m26s
2025-10-29 21:37:14 +08:00
typist
ff8d497f97 refactor: simplify SidebarFooter component
- Removed the 'need more tools?' link from the SidebarFooter.
- Updated the layout of SidebarFooter to use a flex column for better alignment.
2025-10-29 21:36:44 +08:00
typist
9c2799f7d5 0.0.27
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m14s
2025-10-29 14:49:15 +08:00
typist
dd70f9d886 feat: enhance caching strategies for Google Fonts
- Added caching configurations for Google Fonts stylesheets and webfonts.
- Implemented 'StaleWhileRevalidate' for stylesheets and 'CacheFirst' for font files to optimize loading and improve performance.
2025-10-29 14:49:06 +08:00
typist
35cccf6a8f 0.0.26
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m29s
2025-10-29 14:45:03 +08:00
c5616600fa Merge pull request 'feat: add Google Fonts for improved typography' (#11) from feat/update-fonts into main
Reviewed-on: #11
2025-10-29 14:43:59 +08:00
typist
986708fbb4 feat: add Google Fonts for improved typography
- Included preconnect links for Google Fonts to optimize loading.
- Added 'Roboto Mono' and 'Noto Sans SC' font families to enhance text presentation in the application.
2025-10-29 14:44:30 +08:00
typist
6a1b68ed2c 0.0.25
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m32s
2025-10-29 10:05:58 +08:00
typist
32970acf32 chore: support Cloudflare Web Analytics 2025-10-29 10:05:43 +08:00
typist
812bb8c248 0.0.24
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m21s
2025-10-29 09:35:10 +08:00
typist
09f9e6588f fix: update caching strategy for ipinfo API in Vite config
- Changed caching handler from 'CacheFirst' to 'NetworkFirst' to prioritize network responses.
- This adjustment aims to improve data freshness while still utilizing caching effectively.
2025-10-29 09:34:59 +08:00
typist
10a167febd chore: remove SEO-README.md as part of project restructuring
- Deleted the SEO-README.md file which contained outdated SEO optimization details and instructions.
- This change is part of a broader effort to streamline project documentation and improve clarity.
2025-10-29 09:27:54 +08:00
typist
91c0686a46 0.0.23
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m11s
2025-10-29 09:24:17 +08:00
typist
40bfde8e57 feat: implement ID generator component for UUIDs and Nano ID
- Added IDGenerator component to handle the display and regeneration of UUIDs and Nano ID.
- Integrated clipboard functionality to copy generated IDs with user feedback via toast notifications.
- Refactored the Tool component to utilize the new IDGenerator for improved user interaction and code organization.
2025-10-29 09:24:01 +08:00
typist
b4ba7a2219 0.0.22
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m52s
2025-10-29 09:11:11 +08:00
typist
25e42e3af5 fix: update caching strategy for ipinfo API in Vite config
- Changed caching handler from 'NetworkFirst' to 'CacheFirst' for improved performance.
- Extended cache expiration time from 5 minutes to 1 hour to enhance data availability.
2025-10-29 09:10:59 +08:00
typist
4398b53ea7 0.0.21
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m14s
2025-10-29 09:07:27 +08:00
typist
3e14bc652f feat: add terser for advanced minification and improve React chunking
- Added terser as a dependency for enhanced code minification.
- Updated Vite configuration to enable aggressive minification using terser with options to drop console logs and unused code.
- Refined manual chunking for React libraries to improve code splitting and loading efficiency.
2025-10-29 09:06:56 +08:00
9 changed files with 188 additions and 205 deletions

View File

@@ -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

View File

@@ -18,6 +18,11 @@
<link rel="dns-prefetch" href="https://ipinfo.io"> <link rel="dns-prefetch" href="https://ipinfo.io">
<link rel="preconnect" href="https://ipinfo.io" crossorigin> <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 --> <!-- Open Graph / Facebook -->
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://litek.typist.cc/" /> <meta property="og:url" content="https://litek.typist.cc/" />
@@ -59,5 +64,9 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <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> </body>
</html> </html>

View File

@@ -1,7 +1,7 @@
{ {
"name": "litek", "name": "litek",
"private": true, "private": true,
"version": "0.0.20", "version": "0.0.29",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -44,6 +44,7 @@
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.22", "eslint-plugin-react-refresh": "^0.4.22",
"globals": "^16.4.0", "globals": "^16.4.0",
"terser": "^5.44.0",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",

3
pnpm-lock.yaml generated
View File

@@ -96,6 +96,9 @@ importers:
globals: globals:
specifier: ^16.4.0 specifier: ^16.4.0
version: 16.4.0 version: 16.4.0
terser:
specifier: ^5.44.0
version: 5.44.0
tsx: tsx:
specifier: ^4.19.2 specifier: ^4.19.2
version: 4.20.6 version: 4.20.6

View File

@@ -114,11 +114,7 @@ export const AppSidebar = () => {
</SidebarGroupContent> </SidebarGroupContent>
</SidebarGroup> </SidebarGroup>
</SidebarContent> </SidebarContent>
<SidebarFooter className="flex flex-row justify-between items-center gap-2"> <SidebarFooter className="flex flex-col gap-2" />
<SidebarMenuButton variant="outline" asChild>
<a href="mailto:litek@mail.typist.cc">need more tools?</a>
</SidebarMenuButton>
</SidebarFooter>
</Sidebar> </Sidebar>
); );
}; };

View File

@@ -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 * as uuid from 'uuid'
import { nanoid } from 'nanoid' 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 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 ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<span className="text-sm text-muted-foreground">Refresh the page to generate new UUID</span> <span className="text-sm text-muted-foreground">Click the refresh button to regenerate the corresponding ID</span>
<label>UUID Version 1</label>
<span>{uuid.v1()}</span> <IDGenerator
<label>UUID Version 4</label> label="UUID Version 1"
<span>{uuid.v4()}</span> value={uuidV1}
<label>UUID Version 6</label> onRegenerate={() => setUuidV1(uuid.v1())}
<span>{uuid.v6()}</span> />
<label>UUID Version 7</label>
<span>{uuid.v7()}</span> <IDGenerator
<label>Nano ID</label> label="UUID Version 4"
<span>{nanoid()}</span> 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> </div>
); );
}; };

View File

@@ -116,6 +116,7 @@
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-family: 'Roboto Mono', 'Noto Sans SC', 'SF Mono', Consolas, monospace;
} }
} }

View File

@@ -2,7 +2,7 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { Toaster } from '@/components/ui/sonner' import { Toaster } from '@/components/ui/sonner'
import { toast } from 'sonner'
import './index.css' import './index.css'
import { AppRouter } from './router' import { AppRouter } from './router'
@@ -19,11 +19,32 @@ if ('serviceWorker' in navigator && import.meta.env.PROD) {
import('workbox-window').then(({ Workbox }) => { import('workbox-window').then(({ Workbox }) => {
const wb = new Workbox('/sw.js') const wb = new Workbox('/sw.js')
wb.addEventListener('installed', (event) => { // 检测到新版本时,在后台下载完成后显示通知
if (event.isUpdate) { wb.addEventListener('waiting', () => {
console.log('New service worker installed, reloading page...') // 显示更新通知,右上角弹窗
window.location.reload() 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() wb.register()

View File

@@ -32,12 +32,42 @@ export default defineConfig({
runtimeCaching: [ runtimeCaching: [
{ {
urlPattern: /^https:\/\/ipinfo\.io\/.*/i, urlPattern: /^https:\/\/ipinfo\.io\/.*/i,
handler: 'NetworkFirst', handler: "NetworkFirst",
options: { options: {
cacheName: 'ipinfo-cache', cacheName: 'ipinfo-cache',
expiration: { expiration: {
maxEntries: 10, maxEntries: 10,
maxAgeSeconds: 60 * 5 // 5 分钟 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: { cacheableResponse: {
statuses: [0, 200] statuses: [0, 200]
@@ -46,8 +76,8 @@ export default defineConfig({
} }
], ],
cleanupOutdatedCaches: true, cleanupOutdatedCaches: true,
clientsClaim: true, clientsClaim: true, // 新 SW 激活后立即接管
skipWaiting: true skipWaiting: false // 不自动跳过等待,需要手动触发
} }
}) })
], ],
@@ -60,11 +90,15 @@ export default defineConfig({
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: (id) => { manualChunks: (id) => {
// React核心 // React 核心拆分得更细
if (id.includes('node_modules/react') || if (id.includes('node_modules/react/') && !id.includes('node_modules/react-dom')) {
id.includes('node_modules/react-dom') || return 'react-core';
id.includes('node_modules/react-router-dom')) { }
return 'react-vendor'; if (id.includes('node_modules/react-dom/')) {
return 'react-dom';
}
if (id.includes('node_modules/react-router-dom')) {
return 'react-router';
} }
// Radix UI组件 // Radix UI组件
if (id.includes('node_modules/@radix-ui')) { if (id.includes('node_modules/@radix-ui')) {
@@ -74,9 +108,26 @@ export default defineConfig({
if (id.includes('node_modules/lucide-react')) { if (id.includes('node_modules/lucide-react')) {
return 'icons'; 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, chunkSizeWarningLimit: 500,
}, },
}) })