Skip to content

Commit dda010f

Browse files
feat(seo): add landing page with prerendering and SEO enhancements
- Implement landing page with sections for features, how-it-works, use cases, FAQ, and call-to-action - Add prerendering capabilities using vite-prerender-plugin for improved SEO performance - Generate robots.txt and sitemap.xml dynamically during build process - Integrate react-helmet-async for dynamic meta tags and structured data (JSON-LD) - Create separate SPA entry point (spa.html) for client-side routing while maintaining SSR for root - Update nginx configuration with proper caching headers and routing rules - Add Portuguese language support and accessibility improvements
1 parent 6f5aef0 commit dda010f

24 files changed

Lines changed: 1004 additions & 20 deletions

apps/web/index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html lang="pt-BR">
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>Twitch Chat Visualizer v2</title>
6+
<meta name="theme-color" content="#0f172a" />
7+
<title>Overlay de Chat Twitch para OBS | Twitch Chat Visualizer</title>
78
</head>
89
<body>
910
<div id="root"></div>
11+
<noscript>Ative o JavaScript para usar o gerador e o overlay do Twitch Chat Visualizer.</noscript>
1012
<script type="module" src="/src/main.tsx"></script>
1113
</body>
1214
</html>

apps/web/nginx.conf

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,31 @@ server {
33
server_name localhost;
44
root /usr/share/nginx/html;
55
index index.html;
6+
charset utf-8;
7+
8+
add_header X-Content-Type-Options nosniff always;
9+
10+
location = / {
11+
try_files /index.html =404;
12+
add_header Cache-Control "public, max-age=300, must-revalidate";
13+
}
14+
15+
location /assets/ {
16+
try_files $uri =404;
17+
add_header Cache-Control "public, max-age=31536000, immutable";
18+
}
19+
20+
location = /robots.txt {
21+
add_header Cache-Control "public, max-age=3600";
22+
}
23+
24+
location = /sitemap.xml {
25+
add_header Cache-Control "public, max-age=3600";
26+
}
627

728
location / {
8-
try_files $uri $uri/ /index.html;
29+
try_files $uri $uri/ /spa.html;
30+
add_header Cache-Control "public, max-age=300, must-revalidate";
931
}
1032

1133
location /api/ {
@@ -19,4 +41,4 @@ server {
1941
proxy_set_header Upgrade $http_upgrade;
2042
proxy_set_header Connection "upgrade";
2143
}
22-
}
44+
}

apps/web/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "tsc && vite build",
8+
"build": "node ./scripts/generate-seo-files.mjs && tsc && vite build",
99
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1010
"preview": "vite preview",
1111
"test": "vitest run"
@@ -16,6 +16,7 @@
1616
"lucide-react": "^1.12.0",
1717
"react": "^19.2.5",
1818
"react-dom": "^19.2.5",
19+
"react-helmet-async": "^3.0.0",
1920
"react-router-dom": "^7.14.2",
2021
"socket.io-client": "^4.8.3",
2122
"tailwind-merge": "^3.5.0",
@@ -34,6 +35,7 @@
3435
"tailwindcss": "^4.2.4",
3536
"typescript": "^5.2.2",
3637
"vite": "^6.4.2",
38+
"vite-prerender-plugin": "^0.5.13",
3739
"vitest": "^1.6.1"
3840
}
3941
}

apps/web/public/og-image.svg

Lines changed: 26 additions & 0 deletions
Loading

apps/web/public/robots.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
User-agent: *
2+
Allow: /
3+
4+
Sitemap: https://SEU_DOMINIO_AQUI.com/sitemap.xml

apps/web/public/sitemap.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3+
<url>
4+
<loc>https://SEU_DOMINIO_AQUI.com/</loc>
5+
<changefreq>weekly</changefreq>
6+
<priority>1.0</priority>
7+
</url>
8+
<url>
9+
<loc>https://SEU_DOMINIO_AQUI.com/transparent</loc>
10+
<changefreq>monthly</changefreq>
11+
<priority>0.6</priority>
12+
</url>
13+
</urlset>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { mkdir, writeFile } from 'node:fs/promises';
2+
import path from 'node:path';
3+
4+
const defaultSiteUrl = 'https://SEU_DOMINIO_AQUI.com';
5+
const siteUrl = (process.env.VITE_SITE_URL || defaultSiteUrl).replace(/\/+$/, '');
6+
const publicDir = path.resolve(process.cwd(), 'public');
7+
8+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
9+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
10+
<url>
11+
<loc>${siteUrl}/</loc>
12+
<changefreq>weekly</changefreq>
13+
<priority>1.0</priority>
14+
</url>
15+
<url>
16+
<loc>${siteUrl}/transparent</loc>
17+
<changefreq>monthly</changefreq>
18+
<priority>0.6</priority>
19+
</url>
20+
</urlset>
21+
`;
22+
23+
const robots = `User-agent: *
24+
Allow: /
25+
26+
Sitemap: ${siteUrl}/sitemap.xml
27+
`;
28+
29+
await mkdir(publicDir, { recursive: true });
30+
await writeFile(path.join(publicDir, 'robots.txt'), robots, 'utf8');
31+
await writeFile(path.join(publicDir, 'sitemap.xml'), sitemap, 'utf8');

apps/web/spa.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="pt-BR">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta name="theme-color" content="#0f172a" />
7+
<title>Twitch Chat Visualizer</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<noscript>Ative o JavaScript para usar o gerador e o overlay do Twitch Chat Visualizer.</noscript>
12+
<script type="module" src="/src/main.tsx"></script>
13+
</body>
14+
</html>

apps/web/src/App.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
2-
import { SettingsPage } from './pages/SettingsPage';
3-
import { OverlayPage } from './pages/OverlayPage';
1+
import { BrowserRouter } from 'react-router-dom';
2+
import { AppRoutes } from './AppRoutes';
43

54
function App() {
65
return (
76
<BrowserRouter>
8-
<Routes>
9-
<Route path="/" element={<Navigate to="/transparent" replace />} />
10-
<Route path="/transparent" element={<SettingsPage />} />
11-
<Route path="/:channel" element={<Navigate to="/:channel/transparent" replace />} />
12-
<Route path="/:channel/transparent" element={<OverlayPage />} />
13-
</Routes>
7+
<AppRoutes />
148
</BrowserRouter>
159
);
1610
}
1711

18-
export default App;
12+
export default App;

apps/web/src/AppRoutes.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Navigate, Route, Routes, useParams } from 'react-router-dom';
2+
import { LandingPage } from './pages/Landing/LandingPage';
3+
import { SettingsPage } from './pages/SettingsPage';
4+
import { OverlayPage } from './pages/OverlayPage';
5+
6+
function ChannelRedirect() {
7+
const { channel } = useParams<{ channel: string }>();
8+
9+
if (!channel) {
10+
return <Navigate to="/transparent" replace />;
11+
}
12+
13+
return <Navigate to={`/${channel}/transparent`} replace />;
14+
}
15+
16+
export function AppRoutes() {
17+
return (
18+
<Routes>
19+
<Route path="/" element={<LandingPage />} />
20+
<Route path="/transparent" element={<SettingsPage />} />
21+
<Route path="/:channel" element={<ChannelRedirect />} />
22+
<Route path="/:channel/transparent" element={<OverlayPage />} />
23+
</Routes>
24+
);
25+
}

0 commit comments

Comments
 (0)