Skip to content

Commit 07e3ce1

Browse files
authored
Feat: dynamic seo meta tags (#207)
* feat: implement dynamic SEO meta tags * feat: add sitemap.xml and robots.txt for SEO * feat: add on-page SEO optimization with structured data * chore: update branding to Up-Skill * chore: update react-router-dom to fix security vulnerabilities
1 parent ad9c80a commit 07e3ce1

18 files changed

Lines changed: 297 additions & 40 deletions

index.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<!doctype html>
2-
<html lang="en">
2+
<html lang="es">
33

44
<head>
55
<meta charset="UTF-8" />
66
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
77
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8-
9-
<title>UpSkill</title>
10-
8+
9+
<title>Up-Skill - Plataforma de Cursos Online</title>
10+
<meta name="description" content="Aprende nuevas habilidades con cursos online de calidad. Programación, desarrollo web y más con profesores expertos." />
11+
1112
<link rel="preconnect" href="https://fonts.googleapis.com">
1213
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1314
<link

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc -b && vite build",
9+
"prebuild": "node scripts/generate-sitemap.js",
910
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1011
"preview": "vite preview",
1112
"tailwind:init": "tailwindcss init -p",
@@ -19,7 +20,8 @@
1920
"build-storybook": "storybook build",
2021
"cy:run": "cypress run",
2122
"cy:open": "cypress open",
22-
"test:e2e:local": "start-server-and-test preview http://localhost:4173 cy:run"
23+
"test:e2e:local": "start-server-and-test preview http://localhost:4173 cy:run",
24+
"sitemap": "node scripts/generate-sitemap.js"
2325
},
2426
"dependencies": {
2527
"@blocknote/core": "^0.37.0",
@@ -33,9 +35,10 @@
3335
"lucide-react": "^0.534.0",
3436
"react": "^19.1.0",
3537
"react-dom": "^19.1.0",
38+
"react-helmet-async": "^2.0.5",
3639
"react-hook-form": "^7.62.0",
3740
"react-hot-toast": "^2.6.0",
38-
"react-router-dom": "^7.7.0",
41+
"react-router-dom": "^7.12.0",
3942
"recharts": "^3.1.2",
4043
"tailwind-merge": "^3.3.1",
4144
"three": "^0.179.1",

pnpm-lock.yaml

Lines changed: 49 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/img/og-default.png

1.01 MB
Loading

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://up-skill.app/sitemap.xml

public/sitemap.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
3+
xmlns:xhtml="http://www.w3.org/1999/xhtml">
4+
5+
<url>
6+
<loc>https://up-skill.app/</loc>
7+
<lastmod>2026-01-13</lastmod>
8+
<changefreq>weekly</changefreq>
9+
<priority>1.0</priority>
10+
</url>
11+
<url>
12+
<loc>https://up-skill.app/about</loc>
13+
<lastmod>2026-01-13</lastmod>
14+
<changefreq>monthly</changefreq>
15+
<priority>0.8</priority>
16+
</url>
17+
<url>
18+
<loc>https://up-skill.app/faq</loc>
19+
<lastmod>2026-01-13</lastmod>
20+
<changefreq>monthly</changefreq>
21+
<priority>0.7</priority>
22+
</url>
23+
<url>
24+
<loc>https://up-skill.app/contact</loc>
25+
<lastmod>2026-01-13</lastmod>
26+
<changefreq>monthly</changefreq>
27+
<priority>0.7</priority>
28+
</url>
29+
<url>
30+
<loc>https://up-skill.app/courses</loc>
31+
<lastmod>2026-01-13</lastmod>
32+
<changefreq>daily</changefreq>
33+
<priority>0.9</priority>
34+
</url>
35+
<url>
36+
<loc>https://up-skill.app/login</loc>
37+
<lastmod>2026-01-13</lastmod>
38+
<changefreq>yearly</changefreq>
39+
<priority>0.5</priority>
40+
</url>
41+
<url>
42+
<loc>https://up-skill.app/register</loc>
43+
<lastmod>2026-01-13</lastmod>
44+
<changefreq>yearly</changefreq>
45+
<priority>0.5</priority>
46+
</url>
47+
</urlset>

scripts/generate-sitemap.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env node
2+
3+
import fs from 'fs';
4+
import path from 'path';
5+
import { fileURLToPath } from 'url';
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
10+
const baseUrl = 'https://up-skill.app';
11+
const currentDate = new Date().toISOString().split('T')[0];
12+
13+
const staticRoutes = [
14+
{ path: '/', priority: '1.0', changefreq: 'weekly' },
15+
{ path: '/about', priority: '0.8', changefreq: 'monthly' },
16+
{ path: '/faq', priority: '0.7', changefreq: 'monthly' },
17+
{ path: '/contact', priority: '0.7', changefreq: 'monthly' },
18+
{ path: '/courses', priority: '0.9', changefreq: 'daily' },
19+
{ path: '/login', priority: '0.5', changefreq: 'yearly' },
20+
{ path: '/register', priority: '0.5', changefreq: 'yearly' },
21+
];
22+
23+
const generateSitemap = () => {
24+
const urls = staticRoutes.map(route => `
25+
<url>
26+
<loc>${baseUrl}${route.path}</loc>
27+
<lastmod>${currentDate}</lastmod>
28+
<changefreq>${route.changefreq}</changefreq>
29+
<priority>${route.priority}</priority>
30+
</url>`).join('');
31+
32+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
33+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
34+
xmlns:xhtml="http://www.w3.org/1999/xhtml">
35+
${urls}
36+
</urlset>`;
37+
38+
const sitemapPath = path.join(__dirname, '../public/sitemap.xml');
39+
fs.writeFileSync(sitemapPath, sitemap.trim());
40+
console.log('✓ Sitemap generated successfully at public/sitemap.xml');
41+
};
42+
43+
generateSitemap();

src/components/common/SEO.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Helmet } from 'react-helmet-async';
2+
3+
interface SEOProps {
4+
title: string;
5+
description: string;
6+
keywords?: string;
7+
ogType?: 'website' | 'article';
8+
ogImage?: string;
9+
canonical?: string;
10+
}
11+
12+
export function SEO({
13+
title,
14+
description,
15+
keywords,
16+
ogType = 'website',
17+
ogImage = '/img/og-default.png',
18+
canonical,
19+
}: SEOProps) {
20+
const fullTitle = `${title} | Up-Skill`;
21+
const siteUrl = 'https://up-skill.app';
22+
const canonicalUrl = canonical || typeof window !== 'undefined' ? window.location.href : siteUrl;
23+
24+
return (
25+
<Helmet>
26+
<title>{fullTitle}</title>
27+
<meta name="description" content={description} />
28+
{keywords && <meta name="keywords" content={keywords} />}
29+
<link rel="canonical" href={canonicalUrl} />
30+
31+
<meta property="og:type" content={ogType} />
32+
<meta property="og:title" content={fullTitle} />
33+
<meta property="og:description" content={description} />
34+
<meta property="og:image" content={`${siteUrl}${ogImage}`} />
35+
<meta property="og:url" content={canonicalUrl} />
36+
<meta property="og:site_name" content="UpSkill" />
37+
38+
<meta name="twitter:card" content="summary_large_image" />
39+
<meta name="twitter:title" content={fullTitle} />
40+
<meta name="twitter:description" content={description} />
41+
<meta name="twitter:image" content={`${siteUrl}${ogImage}`} />
42+
43+
<meta name="robots" content="index, follow" />
44+
<meta name="language" content="es" />
45+
<meta name="author" content="UpSkill" />
46+
</Helmet>
47+
);
48+
}

src/components/landing/BenefitsSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function BenefitsSection() {
2222
return (
2323
<div key={index} className="p-6 text-center space-y-4 rounded-lg bg-white/70 backdrop-blur-sm border border-slate-200 hover:border-blue-200 hover:shadow-md transition-all duration-300">
2424
<div className="w-12 h-12 bg-gradient-to-br from-blue-400 to-green-400 rounded-full flex items-center justify-center mx-auto shadow-lg">
25-
<IconComponent className="w-6 h-6 text-white" />
25+
<IconComponent className="w-6 h-6 text-white" aria-hidden="true" />
2626
</div>
2727
<h3 className="text-lg font-poppins font-semibold text-slate-800">{benefit.title}</h3>
2828
<p className="text-sm text-slate-600 leading-relaxed">{benefit.description}</p>

src/components/landing/HeroSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default function HeroSection() {
1616
<div className="space-y-6 text-center lg:text-left">
1717
<div className="space-y-4">
1818
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-poppins font-bold text-slate-800 leading-tight">
19-
Aprende sin{' '}
19+
Cursos Online - Aprende sin{' '}
2020
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-green-500">
2121
límites
2222
</span>

0 commit comments

Comments
 (0)