SEO component
Preparing (env variables)
Section titled “Preparing (env variables)”Before starting, be sure to have the env variable VITE_BASE_URL set in the .env file, with the current domain of the project.
VITE_BASE_URL=https://domain.comBase component
Section titled “Base component”For easy manage the seo (metadata tags) for each page, we have a component that can be used.
You should have a src/components/utils/base/BaseSEO.astro file, with the following content:
---interface Props { currentPage: string; // Current page jsonType: string; // Type of the page extraJson?: Record<string, any>; // Extra JSON data baseDescription?: string; // Description of the page (optional, to ignore i18n) baseKeywords?: string; // Keywords of the page (optional, to ignore i18n) author?: string; // Author of the page (optional, to ignore i18n) useTagLine?: boolean; // Use tag line from i18n}const { jsonType, extraJson = {}, baseDescription = "", baseKeywords = "", author = "Dari Dev's Team",} = Astro.props as Props;
// Dataimport { email, socialNetworks, googleMapsLink } from "../../../libs/constants/contact";
const domain = import.meta.env.VITE_BASE_URL;
// Get page metadata (no i18n)const title = "Socialia";const description = baseDescription;const image = `${domain}/banner.webp`;
const twitterHandle = "@DeveloperDari";let keywords = baseKeywords;
// Split keywords into an array if it's a stringconst keywordsContent = Array.isArray(keywords) ? keywords.join(", ") : keywords;
// Generate dataconst jsonLd = { "@context": "https://schema.org", "@type": jsonType || "EducationalOrganization", "@id": `#website`, name: title, description: description, url: domain, logo: `${domain}/logo.png`, image: `${domain}/banner.webp`, email: email, serviceArea: ["Canarias", "España"], hasMap: googleMapsLink, sameAs: [...socialNetworks.map((social) => social.link)], areaServed: [ { "@type": "AdministrativeArea", name: "Canarias" }, { "@type": "Country", name: "España" }, ], inLanguage: "es", ...extraJson,};
const ogTypeMap = { LocalBusiness: "website", Blog: "blog", BlogPosting: "article",};const ogType = ogTypeMap[jsonType as keyof typeof ogTypeMap] || "website";---
<!-- Html tags --><title>{title}</title><meta name="description" content={description} />{keywordsContent && <meta name="keywords" content={keywordsContent} />}<meta name="author" content={author} />
<!-- Open Graph tags --><meta property="og:type" content={ogType} /><meta property="og:title" content={title} /><meta property="og:description" content={description} /><meta property="og:url" content={domain} /><meta property="og:image" content={image} /><!-- this auto generate with platform like facebook, instagram, twitter, etc. --><meta property="og:site_name" content={title} />
<!-- Twitter tags --><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:site" content={twitterHandle} /><meta name="twitter:title" content={title} /><meta name="twitter:description" content={description} /><meta name="twitter:image" content={image} /><meta name="twitter:creator" content="@DeveloperDari" />
<!-- Json Ld --><script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
<!-- Sitemap --><link rel="sitemap" href="/sitemap-index.xml" />Be sure to update all required data
As you can see, this component auto renders the main metadata tags: vanilla html tags, open graph tags, twitter tags, json ld and sitemap.
Constants
Section titled “Constants”Like in the last component, if you need fixed data, you can use the constants file. Example:
export const email = 'contact@socialia.com'export const socialNetworks = [ { name: 'Facebook', link: 'https://www.facebook.com/socialia', },]export const googleMapsLink = 'https://www.google.com/maps/place/Socialia'PageSeo component
Section titled “PageSeo component”We can extend the base component to create a page seo component, or blog post seo component, etc. The main idea its to send extra json data to the component, to make it more dynamic and reusable.
Also we can use the src/libs/constants to reuse data between components and seo
Each specific json data change for the business or project.
---// Componentsimport BaseSEO from './base/BaseSEO.astro'
// example of data (reused from components)import { plansData } from '../../libs/constants/plans'import { heroTitle, heroDescription } from '../../libs/constants/hero'
// Read propsinterface Props { currentPage: string // Current page pageDescription: string // Description extraKeywords?: string // Extra keywords}const { currentPage, pageDescription, extraKeywords = '',} = Astro.props as Props
// Environment variablesconst domain = import.meta.env.VITE_BASE_URL
// Description and keywordsconst defaultDescription = `${heroTitle} ${heroDescription}`const defaultKeywords = 'trabajo social canarias, oposiciones trabajo social canarias, temario trabajo social, casos prácticos trabajo social, estudio trabajo social, preparación oposiciones trabajo social, examen trabajo social canarias, temario canario trabajo social, generador casos prácticos IA, tests trabajo social, ejercicios oposiciones, preguntas trabajo social, recursos trabajo social canarias, preparar oposiciones, convocatorias trabajo social, plaza trabajo social canarias'const description = pageDescription || defaultDescriptionconst keywords = `${defaultKeywords}, ${extraKeywords}`
// addressElems = {// streetAddress: "Cto. Colonias 118",// addressLocality: "Mérida",// addressRegion: "Yucatán",// postalCode: "97100",// addressCountry: "Mexico"// }
// const phoneUnformatted = '+52 999 448 6075'// const phone = phoneUnformatted.replace(/\s/g, '').replace('+', '')// const openingHours = ['Mo-Fr 10:00-20:00', 'Sa 11:00-17:00']// const openingHoursSpecification = [// {// '@type': 'OpeningHoursSpecification',// dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],// opens: '10:00',// closes: '20:00',// },// {// '@type': 'OpeningHoursSpecification',// dayOfWeek: 'Saturday',// opens: '11:00',// closes: '17:00',// }// ]
// Extra JSONconst extraJson = { // Price priceRange: '€€',
// Contact // telephone: phoneUnformatted,
// Hours // openingHours: openingHours, // openingHoursSpecification: openingHoursSpecification,
// Services makesOffer: [ ...plansData.map((plan) => ({ '@type': 'Offer', name: plan.name, description: plan.description, url: `${domain}/pricing`, priceCurrency: 'EUR', price: plan.price, availability: 'https://schema.org/InStock', category: 'Servicio de Preparación de Oposiciones', })), ],
// Founder foundingDate: '2025', founder: { '@type': 'Organization', name: 'Socialia', description: 'Plataforma de preparación de oposiciones de Trabajo Social en Canarias', },
// rating aggregateRating: { '@type': 'AggregateRating', ratingValue: '5.0', reviewCount: '75', },
// Keywords adicionales para el JSON-LD keywords: 'trabajo social canarias, oposiciones trabajo social, preparación oposiciones, casos prácticos trabajo social, temario canario',
// // Location // address: { // '@type': 'PostalAddress', // ...addressElems, // }, // geo: { // '@type': 'GeoCoordinates', // latitude: 20.9984621, // longitude: -89.6152096, // },}---
<BaseSEO currentPage={currentPage} jsonType='EducationalOrganization' extraJson={extraJson} description={description} keywords={keywords} author='Socialia Team'/>Using the component in a page
Section titled “Using the component in a page”After create the component, be sure to import and use it in any page (or the required scope, like using the PostSEO component in each blog post page)
---// Fontsimport '@fontsource-variable/inter'import '@fontsource/belleza'
// Stylesimport '../styles/global.css'
// Componentsimport Header from '../components/organisms/Header'import Footer from '../components/organisms/Footer.astro'---
<!doctype html><html lang='en' data-theme='winter'> <head> <meta charset='UTF-8' /> <meta name='viewport' content='width=device-width' /> <link rel='icon' type='image/svg+xml' href='/favicon.svg' /> <meta name='generator' content={Astro.generator} /> <!-- SEO Slot: dynamic seo in all pages --> <slot name='seo' /> </head> <body class:list={['!bg-base-200']}> <Header /> <main> <slot /> </main> <Footer /> </body></html>And finally, in the page, we can use the SEO component to set the metadata for the page.
---// Layoutimport Layout from '../layouts/Layout.astro'
// Componentsimport HeroHome from '../components/organisms/HeroHome.astro'import InfoCards from '../components/organisms/InfoCards.astro'import Features from '../components/organisms/Features.astro'import PricingCards from '../components/organisms/PricingCards.astro'import Testimonials from '../components/organisms/Testimonials'
// Utils componentsimport PageSEO from '../components/utils/PageSEO.astro'---
<Layout> <!-- SEO component with custom props for the page --> <PageSEO slot="seo" currentPage='Prepara tus Oposiciones de Trabajo Social en Canarias' pageDescription='Plataforma especializada en preparación de oposiciones de Trabajo Social en Canarias. Temario 100% canario, casos prácticos ilimitados generados por IA y herramientas avanzadas para conseguir tu plaza.' extraKeywords='preparación oposiciones trabajo social canarias, temario trabajo social canarias, casos prácticos trabajo social, estudio trabajo social online, plataforma estudio oposiciones' /> <HeroHome /> <InfoCards /> <Features /> <PricingCards /> <Testimonials client:load /></Layout>Sitemap
Section titled “Sitemap”In order to auto generate the sitemap, we use astro sitemap.
Install it and follow the setup, in order to auto generate the sitemap xml files.
Skip the step
Sitemap link in <head>because the BaseSEO.astro component already renders the sitemap link.
In the
Sitemap link in robots.txtuse the alternative with the filesrc/pages/robots.txt.tsinstead ofpublic/robots.txt