Compare commits

...

3 Commits

Author SHA1 Message Date
manuyasm87 5741e68181 fix(seo): redirect non-canonical domains to www.mnqcatering.com
mnqeventos.es (and the bare mnqcatering.com apex) were serving the same
content as www.mnqcatering.com with no redirect, causing duplicate
content. Add middleware that 301-redirects any request whose host isn't
exactly www.mnqcatering.com to the canonical domain, preserving path and
query.

Still needs verification at the reverse-proxy/hosting layer, since host
detection here depends on how the Host header arrives in production.
2026-09-14 19:37:09 +02:00
manuyasm87 5c0ed95e9e feat(seo): add local business address and area served signals
- add PostalAddress and areaServed (Malaga city + province) to the
  sitewide JSON-LD structured data
- show the same address as visible NAP text in the footer and on the
  contact page, matching the schema exactly
- add address and area served to llms.txt for AEO/AI-crawler readability
2026-09-14 19:37:07 +02:00
manuyasm87 9218c5d483 chore(deps): upgrade astro to v7 and bump runtime to node 22
- astro 5.18.1 -> 7.3.2 (fixes critical XSS/RCE advisory GHSA-j687-52p2-xcff)
- @astrojs/node 9.5.5 -> 11.1.5
- typescript pinned to 6.0.3 (astro 7's requirement is satisfied by astro
  itself; typescript 7.x is an incompatible rewrite that breaks
  @astrojs/check, so staying on the 5||6 range it still supports)
- Dockerfile and nixpacks.toml bumped from node:20 to node:22, required by
  astro v6+ (minimum supported node is 22.12.0)
2026-09-14 19:37:05 +02:00
9 changed files with 1737 additions and 3045 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
FROM node:20-alpine AS builder FROM node:22-alpine AS builder
WORKDIR /app WORKDIR /app
@@ -9,7 +9,7 @@ COPY . .
RUN npm run build RUN npm run build
# ── Runtime ────────────────────────────────────────── # ── Runtime ──────────────────────────────────────────
FROM node:20-alpine AS runtime FROM node:22-alpine AS runtime
WORKDIR /app WORKDIR /app
+1 -1
View File
@@ -4,7 +4,7 @@ HOST = "0.0.0.0"
PORT = "4321" PORT = "4321"
[phases.setup] [phases.setup]
nixPkgs = ["nodejs_20"] nixPkgs = ["nodejs_22"]
[start] [start]
cmd = "node ./dist/server/entry.mjs" cmd = "node ./dist/server/entry.mjs"
+1680 -3045
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -12,12 +12,12 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@astrojs/node": "^9.5.5", "@astrojs/node": "^11.1.5",
"@astrojs/sitemap": "^3.7.3", "@astrojs/sitemap": "^3.7.4",
"astro": "^5.18.1" "astro": "^7.3.2"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/check": "^0.9.9", "@astrojs/check": "^0.9.10",
"typescript": "^5.9.3" "typescript": "^6.0.3"
} }
} }
+2
View File
@@ -2,6 +2,8 @@ MNQ Catering y Evento
- Canonical domain: https://www.mnqcatering.com - Canonical domain: https://www.mnqcatering.com
- Business type: Premium catering service - Business type: Premium catering service
- Address: Calle Camino Vivero, 6, 29014, Málaga, España
- Area served: Málaga (city) and Provincia de Málaga
- Services: Bodas, eventos corporativos, reuniones familiares - Services: Bodas, eventos corporativos, reuniones familiares
- Primary contact channels: WhatsApp (+34 678 17 15 13), phone (+34 678 17 15 13) - Primary contact channels: WhatsApp (+34 678 17 15 13), phone (+34 678 17 15 13)
- Key pages: /, /nosotros, /bodas-reales, /corporativo-business, /reuniones-familiares, /contacto - Key pages: /, /nosotros, /bodas-reales, /corporativo-business, /reuniones-familiares, /contacto
+1
View File
@@ -20,6 +20,7 @@ const whatsappAriaLabel = 'Contactar con MNQ por WhatsApp';
<div style="display:flex;flex-direction:column;gap:0.35rem;margin-top:var(--space-4);"> <div style="display:flex;flex-direction:column;gap:0.35rem;margin-top:var(--space-4);">
<a href="tel:+34678171513" class="footer-link" style="width:fit-content;">+34 678 17 15 13</a> <a href="tel:+34678171513" class="footer-link" style="width:fit-content;">+34 678 17 15 13</a>
<a href="https://www.mnqcatering.com" class="footer-link" style="width:fit-content;">www.mnqcatering.com</a> <a href="https://www.mnqcatering.com" class="footer-link" style="width:fit-content;">www.mnqcatering.com</a>
<p class="text-body-sm" style="width:fit-content;">Calle Camino Vivero, 6, 29014, Málaga</p>
</div> </div>
</div> </div>
+20
View File
@@ -42,6 +42,26 @@ const baseStructuredData = {
'Servicio de catering para bodas, eventos corporativos y reuniones familiares con enfoque premium.', 'Servicio de catering para bodas, eventos corporativos y reuniones familiares con enfoque premium.',
telephone: '+34 678 17 15 13', telephone: '+34 678 17 15 13',
serviceType: ['Bodas', 'Eventos corporativos', 'Reuniones familiares'], serviceType: ['Bodas', 'Eventos corporativos', 'Reuniones familiares'],
// Verified business address (owner-confirmed). Geo coordinates not confirmed — left out.
address: {
'@type': 'PostalAddress',
streetAddress: 'Calle Camino Vivero, 6',
postalCode: '29014',
addressLocality: 'Málaga',
addressRegion: 'Málaga',
addressCountry: 'ES',
},
// Kept conservative to the confirmed service area: city + province of Málaga.
areaServed: [
{
'@type': 'City',
name: 'Málaga',
},
{
'@type': 'AdministrativeArea',
name: 'Provincia de Málaga',
},
],
contactPoint: { contactPoint: {
'@type': 'ContactPoint', '@type': 'ContactPoint',
contactType: 'customer service', contactType: 'customer service',
+26
View File
@@ -0,0 +1,26 @@
import { defineMiddleware } from 'astro:middleware';
const CANONICAL_HOST = 'www.mnqcatering.com';
/**
* SEO: this site is undesirably mirrored at mnqeventos.es (same content, no
* redirect at the DNS/hosting level). This middleware enforces the canonical
* host at the application layer for any host that isn't exactly
* www.mnqcatering.com (covers mnqeventos.es, www.mnqeventos.es, and the bare
* mnqcatering.com apex).
*
* NOTE: this must also be verified at the reverse-proxy/hosting layer — host
* detection here depends on how the Host header (or X-Forwarded-Host, when
* behind a proxy) actually arrives in production. If the proxy doesn't
* forward the original host, this check will see the wrong value.
*/
export const onRequest = defineMiddleware((context, next) => {
const requestHost = context.url.host;
if (requestHost !== CANONICAL_HOST) {
const redirectUrl = new URL(context.url.pathname + context.url.search, `https://${CANONICAL_HOST}`);
return context.redirect(redirectUrl.toString(), 301);
}
return next();
});
+8
View File
@@ -93,6 +93,14 @@ const whatsappAriaLabel = 'Contactar con MNQ por WhatsApp';
</div> </div>
</div> </div>
<div class="stack-4">
<span class="label-caps text-primary">Dirección</span>
<div style="display:flex;align-items:center;gap:var(--space-4);">
<Icon name="architecture" class="text-primary" />
<p class="text-muted">Calle Camino Vivero, 6, 29014, Málaga</p>
</div>
</div>
<div class="stack-4"> <div class="stack-4">
<span class="label-caps text-primary">Sitio web</span> <span class="label-caps text-primary">Sitio web</span>
<div style="display:flex;align-items:center;gap:var(--space-4);"> <div style="display:flex;align-items:center;gap:var(--space-4);">