feat(web): SEO base con sitemap, robots.txt y meta tags completos

This commit is contained in:
2026-07-14 13:34:48 +02:00
parent 97ebcdfe03
commit 322704a945
5 changed files with 76 additions and 1 deletions
+3
View File
@@ -1,6 +1,7 @@
// @ts-check
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import sitemap from '@astrojs/sitemap';
// Vite plugin inline: añade Content-Type correcto para .pmtiles y .geojson
// en dev server. Sin esto, Astro/Vite sirve .pmtiles con Content-Type vacío
@@ -27,6 +28,8 @@ function assetMimePlugin() {
// https://astro.build/config
export default defineConfig({
site: 'https://casestancades.vercel.app',
integrations: [sitemap()],
vite: {
plugins: [assetMimePlugin(), tailwindcss()],
},
+1
View File
@@ -12,6 +12,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/sitemap": "^3.7.3",
"astro": "^7.0.9",
"maplibre-gl": "^5.24.0",
"pmtiles": "^4.4.1"
+53
View File
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@astrojs/sitemap':
specifier: ^3.7.3
version: 3.7.3
astro:
specifier: ^7.0.9
version: 7.0.9(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.2)(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0)
@@ -132,6 +135,9 @@ packages:
resolution: {integrity: sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==}
engines: {node: '>=22.12.0'}
'@astrojs/sitemap@3.7.3':
resolution: {integrity: sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==}
'@astrojs/telemetry@3.3.3':
resolution: {integrity: sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==}
engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0}
@@ -885,9 +891,15 @@ packages:
'@types/nlcst@2.0.3':
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
'@types/node@24.13.3':
resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==}
'@types/node@26.1.1':
resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==}
'@types/sax@1.2.7':
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -952,6 +964,9 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -1588,6 +1603,11 @@ packages:
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
sitemap@9.0.1:
resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==}
engines: {node: '>=20.19.5', npm: '>=10.8.2'}
hasBin: true
smol-toml@1.7.0:
resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==}
engines: {node: '>= 18'}
@@ -1599,6 +1619,9 @@ packages:
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
stream-replace-string@2.0.0:
resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -1669,6 +1692,9 @@ packages:
uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
undici-types@8.3.0:
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
@@ -2084,6 +2110,12 @@ snapshots:
dependencies:
prismjs: 1.30.0
'@astrojs/sitemap@3.7.3':
dependencies:
sitemap: 9.0.1
stream-replace-string: 2.0.0
zod: 4.4.3
'@astrojs/telemetry@3.3.3':
dependencies:
ci-info: 4.4.0
@@ -2652,10 +2684,18 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
'@types/node@24.13.3':
dependencies:
undici-types: 7.18.2
'@types/node@26.1.1':
dependencies:
undici-types: 8.3.0
'@types/sax@1.2.7':
dependencies:
'@types/node': 26.1.1
'@types/unist@3.0.3': {}
'@ungap/structured-clone@1.3.3': {}
@@ -2740,6 +2780,8 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.2
arg@5.0.2: {}
argparse@2.0.1: {}
aria-query@5.3.2: {}
@@ -3486,12 +3528,21 @@ snapshots:
sisteransi@1.0.5: {}
sitemap@9.0.1:
dependencies:
'@types/node': 24.13.3
'@types/sax': 1.2.7
arg: 5.0.2
sax: 1.6.0
smol-toml@1.7.0: {}
source-map-js@1.2.1: {}
space-separated-tokens@2.0.2: {}
stream-replace-string@2.0.0: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -3555,6 +3606,8 @@ snapshots:
uncrypto@0.1.3: {}
undici-types@7.18.2: {}
undici-types@8.3.0: {}
unified@11.0.5:
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://casestancades.vercel.app/sitemap-index.xml
+15 -1
View File
@@ -7,17 +7,23 @@ import { t, type Lang } from "../i18n";
interface Props {
title?: string;
description?: string;
image?: string;
active: "home" | "map" | "methodology";
lang?: Lang;
}
const { title, description, active, lang: langProp } = Astro.props;
const { title, description, image, active, lang: langProp } = Astro.props;
const cookieVal = Astro.cookies.get("lang")?.value;
const lang: Lang = langProp ?? (cookieVal === "es" ? "es" : "ca");
const fullTitle = title ? `${title} · ${t(lang, "site.title")}` : t(lang, "site.title");
const fullDescription = description ?? t(lang, "site.description");
const siteTitle = t(lang, "site.title");
const siteUrl = Astro.site?.toString().replace(/\/$/, "") ?? "https://casestancades.vercel.app";
const canonicalUrl = `${siteUrl}${Astro.url.pathname}`;
const ogImage = image ?? `${siteUrl}/og-image.png`;
const ogLocale = lang === "es" ? "es_ES" : "ca_ES";
---
<!doctype html>
@@ -26,15 +32,23 @@ const siteTitle = t(lang, "site.title");
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="canonical" href={canonicalUrl} />
<title>{fullTitle}</title>
<meta name="description" content={fullDescription} />
<meta property="og:title" content={fullTitle} />
<meta property="og:description" content={fullDescription} />
<meta property="og:type" content="website" />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content={siteTitle} />
<meta property="og:locale" content={ogLocale} />
<meta property="og:site_name" content={siteTitle} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={fullTitle} />
<meta name="twitter:description" content={fullDescription} />
<meta name="twitter:image" content={ogImage} />
</head>
<body class="bg-stone-50 text-stone-900">
<Header lang={lang} active={active} />