Commit 5b28b20a authored by fisherdaddy's avatar fisherdaddy

chore: 优化 SEO

parent b113252d
node_modules/ node_modules/
package-lock.json package-lock.json
dist/ dist/
src/.DS_Store src/.DS_Store
\ No newline at end of file !robots.txt
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<meta name="description" content="AI工具箱 - 您的智能助手集合" /> <meta name="description" content="AI工具箱 - 您的智能助手集合" />
<title>AI工具箱 | 智能助手集合</title> <title>AI工具箱 | 智能助手集合</title>
<link rel="stylesheet" href="/src/styles/main.css" /> <link rel="stylesheet" href="/src/styles/main.css" />
<link rel="icon" href="/favicon.ico" type="image/x-icon" /> <link rel="icon" href="/public/favicon.ico" type="image/x-icon" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-GSZMX418JL"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-GSZMX418JL"></script>
......
...@@ -11,11 +11,13 @@ ...@@ -11,11 +11,13 @@
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"styled-components": "^6.1.13" "styled-components": "^6.1.13"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-react": "^4.0.0", "@vitejs/plugin-react": "^4.0.0",
"vite": "^4.3.9" "vite": "^4.3.9",
"vite-plugin-sitemap": "^0.7.1"
} }
} }
User-agent: *
Disallow:
Sitemap: https://fishersama.com/sitemap.xml
\ No newline at end of file
import React from 'react'; import React, { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom'; import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home'; import Home from './pages/Home';
import JsonFormatter from './components/JsonFormatter';
import Header from './components/Header'; import Header from './components/Header';
import Footer from './components/Footer'; import Footer from './components/Footer';
import TextToImage from './components/TextToImage'; import NotFound from './pages/NotFound';
import UrlDecode from './components/UrlDecode';
function App(op) { const JsonFormatter = lazy(() => import('./components/JsonFormatter'));
const TextToImage = lazy(() => import('./components/TextToImage'));
const UrlDecode = lazy(() => import('./components/UrlDecode'));
function App() {
return ( return (
<div className="app-container"> <div className="app-container">
<Header /> <Header />
<div className="content-wrapper"> <div className="content-wrapper">
<main> <main>
<Routes> <Suspense fallback={<div>Loading...</div>}>
<Route path="/" element={<Home />} /> <Routes>
<Route path="/text2image" element={<TextToImage />} /> <Route path="/" element={<Home />} />
<Route path="/json-formatter" element={<JsonFormatter />} /> <Route path="/text2image" element={<TextToImage />} />
<Route path="/url-decode" element={<UrlDecode />} /> <Route path="/json-formatter" element={<JsonFormatter />} />
</Routes> <Route path="/url-decode" element={<UrlDecode />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</main> </main>
</div> </div>
<Footer /> <Footer />
......
// src/components/SEO.jsx
import React from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from '../js/i18n';
function SEO({ title, description, lang = 'en', meta = [] }) {
const { t } = useTranslation();
const defaultTitle = t('title');
const defaultDescription = t('description'); // 确保在i18n配置中添加'description'
const languages = ['en', 'zh', 'ja', 'ko'];
const hostname = 'https://fishersama.com'; // 替换为您的网站域名
const links = languages.map((language) => ({
rel: 'alternate',
hrefLang: language,
href: `${hostname}/${language === 'en' ? '' : language}`,
}));
const structuredData = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": defaultTitle,
"url": "https://fishersama.com/", // 请替换为您的网站URL
"description": defaultDescription,
"potentialAction": {
"@type": "SearchAction",
"target": "https://fishersama.com/search?q={search_term}",
"query-input": "required name=search_term"
}
};
return (
<Helmet
htmlAttributes={{
lang,
}}
title={title || defaultTitle}
titleTemplate={`%s | ${defaultTitle}`}
meta={[
{
name: 'description',
content: description || defaultDescription,
},
{
property: 'og:title',
content: title || defaultTitle,
},
{
property: 'og:description',
content: description || defaultDescription,
},
{
property: 'og:type',
content: 'website',
},
{
name: 'twitter:card',
content: 'summary',
},
{
name: 'twitter:title',
content: title || defaultTitle,
},
{
name: 'twitter:description',
content: description || defaultDescription,
},
// 可以根据需要添加更多元数据
].concat(meta)}
link={links}
>
<script type="application/ld+json">
{JSON.stringify(structuredData)}
</script>
</Helmet>
);
}
export default SEO;
\ No newline at end of file
...@@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; ...@@ -3,6 +3,7 @@ import { useState, useEffect } from 'react';
const i18n = { const i18n = {
en: { en: {
title: 'AI Toolbox', title: 'AI Toolbox',
description: 'Your one-stop solution for various AI tools.',
slogan: 'Your collection of intelligent assistants, solving various AI needs in one place.', slogan: 'Your collection of intelligent assistants, solving various AI needs in one place.',
tools: { tools: {
text2image: { text2image: {
...@@ -28,9 +29,15 @@ const i18n = { ...@@ -28,9 +29,15 @@ const i18n = {
copiedMessage: 'Copied' copiedMessage: 'Copied'
}, },
}, },
notFound: {
title: '404 - Page Not Found',
description: 'Sorry, the page you are looking for does not exist.',
back_home: 'Back to Home'
}
}, },
zh: { zh: {
title: 'AI 工具箱', title: 'AI 工具箱',
description: '一站式解决各种AI工具需求。',
slogan: '您的智能助手集合,一站式解决各种 AI 需求。', slogan: '您的智能助手集合,一站式解决各种 AI 需求。',
tools: { tools: {
text2image: { text2image: {
...@@ -56,9 +63,15 @@ const i18n = { ...@@ -56,9 +63,15 @@ const i18n = {
copiedMessage: '已复制' copiedMessage: '已复制'
}, },
}, },
notFound: {
title: '404 - 页面未找到',
description: '抱歉,您访问的页面不存在。',
back_home: '返回首页'
}
}, },
ja: { ja: {
title: 'AIツールボックス', title: 'AIツールボックス',
description: 'あなたのインテリジェントアシスタントコレクション、様々なAIニーズを一箇所で解決します。',
slogan: 'あなたのインテリジェントアシスタントコレクション、様々なAIニーズを一箇所で解決します。', slogan: 'あなたのインテリジェントアシスタントコレクション、様々なAIニーズを一箇所で解決します。',
tools: { tools: {
text2image: { text2image: {
...@@ -84,9 +97,15 @@ const i18n = { ...@@ -84,9 +97,15 @@ const i18n = {
copiedMessage: 'コピーしました' copiedMessage: 'コピーしました'
}, },
}, },
notFound: {
title: '404 - ページが見つかりません',
description: '申し訳ありませんが、お探しのページは存在しません。',
back_home: 'ホームに戻る'
}
}, },
ko: { ko: {
title: 'AI 도구 상자', title: 'AI 도구 상자',
description: '당신의 지능형 어시스턴트 컬렉션, 다양한 AI 요구 사항을 한 곳에서 해결합니다.',
slogan: '당신의 지능형 어시스턴트 컬렉션, 다양한 AI 요구 사항을 한 곳에서 해결합니다.', slogan: '당신의 지능형 어시스턴트 컬렉션, 다양한 AI 요구 사항을 한 곳에서 해결합니다.',
tools: { tools: {
text2image: { text2image: {
...@@ -112,6 +131,11 @@ const i18n = { ...@@ -112,6 +131,11 @@ const i18n = {
copiedMessage: '복사됨' copiedMessage: '복사됨'
}, },
}, },
notFound: {
title: '404 - 페이지를 찾을 수 없습니다',
description: '죄송합니다. 찾고 있는 페이지가 존재하지 않습니다.',
back_home: '홈으로 돌아가기'
}
}, },
}; };
......
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from '../components/SEO';
const tools = [ const tools = [
{ id: 'text2image', icon: 'fa-image', path: '/text2image' }, { id: 'text2image', icon: 'fa-image', path: '/text2image' },
...@@ -9,28 +9,35 @@ const tools = [ ...@@ -9,28 +9,35 @@ const tools = [
{ id: 'urlDecode', icon: 'fa-decode', path: '/url-decode' }, { id: 'urlDecode', icon: 'fa-decode', path: '/url-decode' },
]; ];
function Home() { const Home = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <>
<section className="hero"> <SEO
{/* 添加 logo */} title={t('title')}
<h1>{t('title')}</h1> description={t('slogan')}
<p className="slogan">{t('slogan')}</p> />
</section> <main>
<section className="tools-section"> <section className="hero">
<div className="tools-grid"> <h1>{t('title')}</h1>
{tools.map(tool => ( <p className="slogan">{t('slogan')}</p>
<Link to={tool.path} key={tool.id} className="tool-card"> </section>
<i className={`fas ${tool.icon} tool-icon`}></i> <section className="tools-section">
<h3 className="tool-title">{t(`tools.${tool.id}.title`)}</h3> <h2>工具</h2>
<p className="tool-description">{t(`tools.${tool.id}.description`)}</p> <div className="tools-grid">
</Link> {tools.map(tool => (
))} <Link to={tool.path} key={tool.id} className="tool-card">
</div> <i className={`fas ${tool.icon} tool-icon`}></i>
</section> <h3 className="tool-title">{t(`tools.${tool.id}.title`)}</h3>
</div> <p className="tool-description">{t(`tools.${tool.id}.description`)}</p>
</Link>
))}
</div>
</section>
</main>
</>
); );
} };
export default Home; export default Home;
// src/pages/NotFound.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import SEO from '../components/SEO';
import { useTranslation } from '../js/i18n';
const NotFound = () => {
const { t } = useTranslation(); // 使用 useTranslation 钩子
return (
<>
<SEO
title={t('notFound.title')}
description={t('notFound.description')}
/>
<main>
<h1>{t('notFound.title')}</h1>
<p>{t('notFound.description')}</p>
<Link to="/">{t('notFound.back_home')}</Link>
</main>
</>
);
};
export default NotFound;
\ No newline at end of file
import { defineConfig } from 'vite' import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react' import sitemap from 'vite-plugin-sitemap';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [
sitemap({
// plugin options
}),
],
server: { server: {
port: 3000 port: 3000
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment