Last updated: 2026-10-05
How the site is made discoverable by search engines and AI assistants, and what to update when a page, a project or a slug changes. Per-language rules live in i18n.md; performance and accessibility have their own documents.
| What | Where |
|---|---|
| Head tags, canonical URL, Open Graph, Twitter, JSON-LD | src/layouts/Layout.astro |
| ES to EN page pairs and language-toggle targets | src/i18n/utils.ts (EN_PAGE_MAP) |
| Sitemap, priorities and sitemap hreflang | astro.config.mjs (@astrojs/sitemap) |
| Crawler directives | public/robots.txt |
| Legacy and renamed URLs | vercel.json (redirects) |
| Project content, dates, schema type, parts | src/data/projects.ts |
| Project page title, meta description and JSON-LD | src/data/projectSeo.ts |
| AI-discovery files | public/llms.txt, public/llms-full.txt |
Layout.astro emits, from the props a page passes (title, description, alternates, ogImage, ogType, noindex, preloadHero):
<title>and<meta name="description">.<meta name="robots">:index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1, ornoindex, nofollowwhennoindexis set.<link rel="canonical">built from the pathname againsthttps://www.wavival.dev. The canonical host is alwayswww.- One
<link rel="alternate" hreflang>per entry ofalternates(es,en,x-default). - Open Graph: type, url, title, description, image (1200x630), image alt,
og:locale(es_COoren_US) with the other one asog:locale:alternate, site name. The type isprofileon the two homes andarticleon case studies, which addarticle:published_timeandarticle:modified_time; every other page iswebsite. - Twitter Card
summary_large_imagewith@wavival0. - Icons,
site.webmanifestandtheme-color.
Default social cards are public/images/og-card-es.webp and og-card-en.webp. Project pages pass their own card (image in projects.ts: public/images/og-*.webp, and forgotten-portal.webp for the Forgotten Portal writeup).
All pages carry a graph with Person, Organization and WebSite. Everything on wavival.dev belongs to wavival: the Person is the entity of the site (it holds the contact email and is the author and publisher of the WebSite and the provider of the services), and the Organization is Lúmina W, a separate company that she founded (founder) and that has its own site, services and pricing at luminaw.co. The Organization carries no contact point or service of this site, and the Person has no worksFor. The Person is a Full Stack Developer (jobTitle) and the description states the focus on backend and AI. On top of it:
| Page type | Extra schema |
|---|---|
Home (/, /en) |
ProfilePage (its mainEntity is the Person) |
| About, tools, projects index | BreadcrumbList |
| Contact | ContactPage (its mainEntity is the Person) and BreadcrumbList |
| Services | Service (provider: the Person), FAQPage and BreadcrumbList |
| Project pages | BreadcrumbList and one project node chosen by schemaType in projects.ts |
| Quote, privacy, 404 | none beyond the shared graph |
Each project has one page and one project node (projectSchema), with the Person as author. It is SoftwareApplication for TerraCore, OKroot and NullBreach, WebSite for Lúmina W and wavival.dev, and CreativeWork for the Forgotten Portal writeup. name is the official name and alternateName is the short name when it differs. On a project with several parts the node adds hasPart, one entry per part that has a link, with its own schema.org type (SoftwareApplication, WebSite, Blog, SoftwareSourceCode, WebAPI or TechArticle), a name such as "TerraCore Landing", the part blurb and its URL. softwareRequirements lists the stack of the app part (the first part when there is no app part); the merged stack of all parts is used only when that part declares none.
The page title is <official name>: Caso de estudio | Valentina Ramírez in Spanish and <official name>: Case study | Valentina Ramirez in English (projectMeta).
robots.txtallows every crawler, disallows/cdn-cgi/and points tohttps://www.wavival.dev/sitemap-index.xml.- The sitemap is generated at build time. Priority is 1.0 for the two homes, 0.8 for regular pages, 0.7 for project pages (six per language) and 0.3 for privacy;
changefreqis monthly. Every URL that has a counterpart listses,enandx-default(the Spanish URL) alternates; each project has a single URL per language, so its hreflang pair is/proyectos/<slug>and/en/projects/<slug>, and a part is only a hash on it, never a separate URL. /404and/en/404arenoindexand have no alternates./nullbreachbelongs to another Vercel application (microfrontends.json) and is not part of this sitemap.- Permanent (301) redirects keep old links alive. Merged slugs go to the part of the ecosystem that replaced them:
terracore-landingto/proyectos/terracore#landing,okroot-landingandroot-landingto/proyectos/okroot#landing,rootto/proyectos/okroot, andblog-lumina-wto/proyectos/lumina-w#blog, each with its/en/projects/...and unprefixed/projects/...equivalent (vercel.json). The unprefixed English paths (/projects,/projects/<slug>,/services,/about,/contact,/uses), which resolve to the Spanish pages.
public/llms.txtis the index: profile, every project with its page URL, the stack, contact data and the English version of the site.public/llms-full.txtis the long-form companion: one section per project, services and contact.- Both state that wavival.dev (services, pricing, contact) belongs to wavival and luminaw.co to Lúmina W, and that the published price (from USD 250 / COP 1,000,000, depending on scope) is wavival's.
llms-full.txtcarries the price and services. - Both are written by hand. A test (
tests/case-studies.spec.ts) fails when a project slug is missing from either file. - Keep both aligned with the site whenever a project, a service, a price or a contact channel changes (rule also in
AGENTS.md).
- New page: add it to
EN_PAGE_MAP(or the project special case), passalternatestoLayout, give it a unique title and a description, add JSON-LD if it has a schema type. - New, merged or renamed project: update
projects.ts(slug,name,parts,en,metaDescriptionup to 160 characters,schemaType, dates), the social card,llms.txtandllms-full.txt, and add a 301 redirect for every slug that was published before, pointing at the part that replaced it. - Run
npm run build && npm test;tests/seo.spec.ts,tests/i18n.spec.tsandtests/redirects.spec.tscover robots, sitemap, hreflang and redirects.
Tracked in ROADMAP.md: lastmod in the sitemap, explicit AI crawlers in robots.txt, and the Forgotten Portal social image size. The quote pages (/cotizar, /en/quote) have short meta descriptions (about 70 characters).