SSR y datos iniciales
<mapvx-website> funciona en sitios renderizados en el servidor (Next.js, Nuxt, SvelteKit,
Angular SSR y otros), pero solo se renderiza en el navegador. Esta página explica qué puede hacer
el servidor por él, el atributo opcional initial-data con su paquete helper, y una receta por
framework.
En resumen: renderiza la etiqueta con sus atributos en el servidor y carga el bundle solo en el
navegador. initial-data es opcional. Hace que las primeras tarjetas de lugares aparezcan antes
de que responda la API, pero no ahorra ninguna solicitud a la API, y en 1.6.1, combinado con
api-key, muestra cada lugar dos veces (problema conocido).
Sin él, el componente carga todo por sí mismo
(configuración solo en el cliente).
Cómo funciona
- El servidor solo genera la etiqueta. Tu framework renderiza
<mapvx-website>con sus atributos y nada adentro. Las tarjetas, los perfiles y el mapa se construyen en el navegador, así que el HTML del servidor no contiene marcado de lugares para los buscadores, con o sininitial-data. Si necesitas contenido indexable, renderízalo tú mismo fuera del elemento. - El elemento reemplaza su contenido. Cuando
main.jsdefine el elemento en el navegador, el componente construye su interfaz dentro de la etiqueta y elimina todo lo que hayas puesto ahí, como un texto de respaldo. - Nunca importes el bundle en código de servidor. Evaluar
main.jsen Node.js lanzaReferenceError: HTMLElement is not defined. Eso incluye los imports de nivel superior en componentes que también se renderizan en el servidor, como los componentes'use client'de Next.js. Cárgalo con un<script type="module">o con unimport()dinámico que solo se ejecute en el navegador. Los imports de solo tipos (import type) son seguros en cualquier lugar, porque se eliminan al compilar. - Con React, cárgalo después de la hidratación. Con React 19, si el elemento se inicializa
(upgrade) antes de que React hidrate la página, React informa un error de hidratación
recuperable y vuelve a crear el árbol en el cliente
(error de hidratación). Cargar el bundle después de la
hidratación lo evita. La receta de Next.js de abajo lo hace así, y en SvelteKit
onMounttambién se ejecuta después de la hidratación. - Los atributos son cadenas de texto. El servidor solo puede pasar atributos, así que los
booleanos deben ser las cadenas
"true"o"false"(atributos booleanos). - Nonce de CSP. Si tu política usa un nonce para los estilos, renderiza el atributo
ngCspNonceen el elemento desde el servidor (Seguridad y privacidad).
Una vez que el bundle se ejecuta en el navegador:
- El elemento se inicializa (upgrade) y lee sus atributos. Si
initial-datacontiene lugares, sus tarjetas se renderizan de inmediato. - Con
api-keydefinido, el componente inicia el SDK de MapVX y solicita los lugares disponibles, la configuración y las instituciones, haya o noinitial-data. - El mapa se crea la primera vez que aparece uno en pantalla (vista de mapa, perfil de un lugar o ruta), una vez que llegó la configuración. La búsqueda, el detalle de los lugares y las rutas también pasan por el SDK.
Las versiones anteriores a 1.5.4 podían mostrar un mapa en blanco en la primera carga de la vista de mapa en sitios renderizados en el servidor (Novedades).
Qué cambia con initial-data
Sin initial-data | Con initial-data | |
|---|---|---|
| HTML del servidor | La etiqueta vacía con sus atributos | Lo mismo, más los datos en un atributo muy grande |
| Primeras tarjetas de lugares | Cuando responde la API | En cuanto el elemento se inicializa |
| Solicitudes a la API desde el navegador | Lugares, configuración, instituciones | Las mismas solicitudes |
| Mapa, búsqueda, detalle de lugares, rutas | Necesitan api-key | Necesitan api-key |
| Tarjetas de lugares en la lista | Una vez cada lugar | Dos veces cada lugar en 1.6.1 (problema conocido) |
initial-data solo adelanta las primeras tarjetas: no reduce el tráfico a la API ni reemplaza a
api-key. Sin api-key, la cuadrícula se renderiza desde initial-data, pero el mapa, la
búsqueda, el detalle de los lugares y las rutas no funcionan.
El valor del atributo
initial-datarecibe una cadena JSON con dos claves,availablePlaceseinstitutions(el tipoInitialData).prepareInitialDatadevuelve exactamente esa cadena. Las demás claves se ignoran.- Un JSON inválido se ignora sin ningún mensaje en la consola en la versión publicada, y el componente carga normalmente desde la API.
- Si en cambio defines la propiedad JS
initialData, también debe ser una cadena: un objeto lanza unTypeErrordentro del componente. - Las pestañas de categorías salen de la institución de los datos cuyo id es exactamente igual a
institution-id, así que pasa el id sin espacios alrededor. - Cada cambio del valor se vuelve a procesar y agrega sus lugares otra vez (ver el problema conocido). Defínelo una sola vez, en el primer render.
- La fila de categorías no arranca con ninguna prendida en ninguno de los dos casos, así que la
cuadrícula se abre sin filtrar, uses o no
initial-data. Para abrir con categorías concretas, agrega?tab=a la URL de la página (deep links).
Referencia del atributo: Atributos.
El paquete helper
@mapvx/web-components-ssr-helpers
prepara la cadena de initial-data en tu servidor. Instálalo junto al componente y fija las
versiones exactas que probaste (0.1.1 es la versión actual del helper):
npm install --save-exact @mapvx/web-components-ssr@1.6.1 @mapvx/web-components-ssr-helpers@0.1.1Su única exportación es prepareInitialData. El paquete incluye builds ESM y CommonJS con
declaraciones de TypeScript, además de un build IIFE para etiquetas script
(dist/helpers.iife.js, global Helper):
ESM
import { prepareInitialData } from '@mapvx/web-components-ssr-helpers'
const initialData = await prepareInitialData('YOUR_API_KEY')Requisitos:
- Un runtime de servidor con
fetchglobal: Node.js 18 o posterior, o un runtime edge o de workers. El helper no tiene dependencias en tiempo de ejecución y no declaraengines. El paquete del componente sí declaraengines.node, que solo afecta la instalación (Requisitos). - Acceso HTTPS saliente desde tu servidor a
https://api.mapvx.com. - Uso en el servidor. El helper también funciona en un navegador, pero ahí no aporta nada: el componente solicita los mismos datos por su cuenta.
No tiene opciones: los datos siempre vienen en inglés, y no hay configuración para la URL base, un tiempo de espera ni una institución. Devuelve todos los lugares e instituciones disponibles para la API key.
Si todavía usas el paquete antiguo @mapvx/website-component-helpers, cámbialo por este: su
versión 0.1.1 lanza un error en lugar de devolver null cuando la API key es inválida o falta
(migración desde 0.x).
prepareInitialData
function prepareInitialData(apiKey: string): Promise<string>Envía dos solicitudes GET en paralelo a la API de MapVX en https://api.mapvx.com: los lugares
disponibles para apiKey y sus instituciones. Ambas llevan la API key y lang=en en el query string.
De todos modos, la API key es pública: el navegador la recibe en el atributo api-key.
Valor de retorno
Se resuelve con una cadena JSON, no con un objeto. Una vez convertida con JSON.parse, la
cadena tiene esta forma:
// InitialData, exportado como tipo por @mapvx/web-components-ssr
interface InitialData {
availablePlaces: PlaceResponse[] | null
institutions: InstitutionResponse[] | null
}PlaceResponse e InstitutionResponse son los objetos crudos de la API. Sus tipos los exporta
@mapvx/web-components-ssr para usarlos con import type (Tipos TypeScript);
el helper solo tipa el resultado como string.
- Pasa la cadena a
initial-datasin modificarla. No hace falta convertirla en objeto y volver a serializarla. - Es grande. Con una API key de 255 lugares y 4 instituciones ocupó unos 557 KB, y unos 830 KB
una vez escapada dentro del atributo HTML (unos 164 KB con gzip). Los frameworks que además
serializan los datos de la página para la hidratación (el payload RSC en Next.js, el payload de
Nuxt, los datos de
loaden SvelteKit,TransferStateen Angular) la envían una segunda vez.
Errores
| Situación | Resultado |
|---|---|
| Error HTTP de la API, por ejemplo 403 por una API key inválida o vacía | El helper registra Fetch failed with status ... con console.error y devuelve null para esa mitad. Con una key inválida, el resultado es {"availablePlaces":null,"institutions":null}. |
| Falla de red (DNS, conexión rechazada, TLS) | La promesa se rechaza. Se pierden ambas mitades y el helper no registra nada. |
| Respuesta exitosa que no es JSON | La promesa se rechaza con un SyntaxError. |
| La API no responde | La promesa queda pendiente. No hay tiempo de espera ni reintentos, así que el render de tu servidor también espera. |
El componente ignora una mitad null; con api-key definido, carga todos sus datos desde la API
de todas formas. Para que una falla o una API lenta no rompan el render, envuelve la llamada con
un tiempo de espera y, si falla, renderiza sin initial-data. Las recetas de abajo importan este
wrapper:
// mapvx-initial-data.ts
import { prepareInitialData } from '@mapvx/web-components-ssr-helpers'
// Se resuelve con la cadena de initial-data, o undefined si no se puede preparar a tiempo.
// Con undefined, renderiza el elemento sin initial-data: carga los datos por sí mismo.
export async function getMapvxInitialData(
apiKey: string,
timeoutMs = 3000,
): Promise<string | undefined> {
let timer: ReturnType<typeof setTimeout> | undefined
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error('MapVX initial data timed out')), timeoutMs)
})
try {
return await Promise.race([prepareInitialData(apiKey), timeout])
} catch {
return undefined
} finally {
clearTimeout(timer)
}
}El tiempo límite solo corta la espera: las solicitudes no se cancelan y terminan en segundo plano.
Caché y vigencia de los datos
- Cada llamada hace dos solicitudes a la API y devuelve cientos de KB; en una medición tardó
alrededor de un segundo y medio. Evita llamarlo en cada solicitud: guarda la cadena en caché en
el servidor por algunos minutos, por ejemplo con la revalidación de tu framework (
revalidateen Next.js). - Si la página se prerenderiza al compilar (Next.js sin
revalidateni renderizado dinámico,RenderMode.Prerenderen Angular, exportaciones estáticas), los datos quedan fijos en el momento del build. De todos modos, el componente carga los datos actuales en el navegador y los agrega, así que los lugares eliminados después del build siguen apareciendo junto a los actuales.
Problema conocido: tarjetas de lugares duplicadas
Cada tarjeta de lugar aparece dos veces cuando se definen initial-data y api-key a la
vez. El componente muestra los lugares de initial-data y luego agrega los lugares de su propia
solicitud a la API sin quitar los duplicados. Esto afecta a la versión actual, 1.6.1. Los
marcadores del mapa no se duplican.
La lista vuelve a crecer cada vez que cambia initial-data o institution-id, y cada vez que el
elemento se vuelve a montar en la misma sesión de la página, por ejemplo después de una navegación
del lado del cliente de vuelta a la página.
Si esto importa en tu sitio, omite initial-data: el componente carga los mismos datos por sí
mismo (configuración solo en el cliente).
Next.js (App Router)
Un Server Component prepara los datos, y un Client Component renderiza el elemento y carga el
bundle desde public/ después de la hidratación.
Instala los paquetes
npm install --save-exact @mapvx/web-components-ssr@1.6.1 @mapvx/web-components-ssr-helpers@0.1.1Copia el bundle en public/
Copia toda la carpeta dist/browser y conserva cada nombre de archivo: cada chunk chunk-*.js
importa ./main.js con ese nombre, así que un main.js renombrado los rompe. La carpeta con
versión evita que las cachés del navegador mezclen archivos de dos versiones
(alojamiento propio).
// scripts/copy-mapvx.mjs
import { cpSync, readFileSync, rmSync } from 'node:fs'
const pkgDir = 'node_modules/@mapvx/web-components-ssr'
const { version } = JSON.parse(readFileSync(`${pkgDir}/package.json`, 'utf8'))
const target = `public/mapvx-website/${version}`
rmSync('public/mapvx-website', { recursive: true, force: true })
// main.js, cada chunk-*.js y styles.css, con sus nombres publicados
cpSync(`${pkgDir}/dist/browser`, target, { recursive: true })
console.log(`Se copió @mapvx/web-components-ssr@${version} en ${target}`)Ejecútalo antes de next dev y next build, por ejemplo con los scripts pre de npm, que npm
ejecuta antes de npm run dev y npm run build. Si tu gestor de paquetes no ejecuta los scripts
pre (por ejemplo, Yarn 2 o posterior), llama al script al comienzo de tus scripts dev y
build. Agrega public/mapvx-website/ a .gitignore si no quieres versionar la copia:
{
"scripts": {
"predev": "node scripts/copy-mapvx.mjs",
"prebuild": "node scripts/copy-mapvx.mjs"
}
}Importa los estilos
// app/layout.tsx
import '@mapvx/web-components-ssr/dist/browser/styles.css'Dale también display: block al elemento (tamaño).
Declara el elemento para TypeScript
El paquete no incluye tipos JSX para <mapvx-website> (tipado del elemento).
Los booleanos se tipan como las cadenas 'true' y 'false' porque React 19 renderiza {true}
como un atributo vacío (que se lee como false) y omite {false} (así que se aplica el valor por
defecto).
// types/mapvx-website-react.d.ts (el tsconfig por defecto de Next.js lo incluye)
import type { DetailedHTMLProps, HTMLAttributes } from 'react'
import type { UserAction } from '@mapvx/web-components-ssr'
type BoolAttr = 'true' | 'false'
export interface MapvxWebsiteAttributes
extends DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> {
'api-key'?: string
'institution-id'?: string
'initial-data'?: string
'default-to-map'?: BoolAttr
'show-category-filters'?: BoolAttr
'show-city-filter'?: BoolAttr
'inherit-font-family'?: BoolAttr
'hide-deals'?: BoolAttr
'show-deal-labels'?: BoolAttr
'card-width'?: number | string
'rows-per-page'?: number | string
onuserAction?: (event: CustomEvent<UserAction>) => void
oncardSelected?: (event: CustomEvent<string>) => void
}
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'mapvx-website': MapvxWebsiteAttributes
}
}
}Crea el Client Component
// app/mapvx-website.tsx
'use client'
import Script from 'next/script'
import type { UserAction } from '@mapvx/web-components-ssr'
// Debe coincidir con la carpeta que crea scripts/copy-mapvx.mjs
const MAPVX_VERSION = '1.6.1'
type Props = {
apiKey: string
institutionId: string
initialData?: string
}
export function MapvxWebsite({ apiKey, institutionId, initialData }: Props) {
return (
<>
{/* next/script lo carga después de la hidratación por defecto (afterInteractive) */}
<Script type="module" src={`/mapvx-website/${MAPVX_VERSION}/main.js`} />
<mapvx-website
api-key={apiKey}
institution-id={institutionId}
initial-data={initialData}
default-to-map="true"
onuserAction={(event: CustomEvent<UserAction>) => console.log(event.detail)}
/>
</>
)
}No hagas import '@mapvx/web-components-ssr' en este archivo (import type sí se puede): los
Client Components también se renderizan en el servidor, donde el bundle lanza un error. En
React 19 la prop onuserAction escucha el evento userAction; onUserAction no
(eventos en frameworks).
Prepara los datos en la página
# .env.local
MAPVX_API_KEY=YOUR_API_KEY
MAPVX_INSTITUTION_ID=YOUR_INSTITUTION_ID// app/page.tsx (Server Component)
import { getMapvxInitialData } from '../lib/mapvx-initial-data'
import { MapvxWebsite } from './mapvx-website'
// Regenera la página como máximo cada 5 minutos. Sin esto, Next.js la prerenderiza una vez, al compilar
export const revalidate = 300
export default async function Page() {
const apiKey = process.env.MAPVX_API_KEY ?? ''
const initialData = await getMapvxInitialData(apiKey)
return (
<MapvxWebsite
apiKey={apiKey}
institutionId={process.env.MAPVX_INSTITUTION_ID ?? ''}
initialData={initialData}
/>
)
}lib/mapvx-initial-data.ts es el wrapper con tiempo de espera. La página se
sigue prerenderizando durante next build y luego se regenera en segundo plano, así que las
variables de entorno también deben estar definidas al compilar, y el build llama a la API de
MapVX: sin acceso de red en ese momento, la página prerenderizada sale sin initial-data hasta que
se regenera. La key llega al navegador de todos modos a través del atributo api-key; leerla en el
servidor solo mantiene la configuración en un solo lugar.
Nuxt
Una ruta de servidor prepara los datos, así que el helper se ejecuta en el servidor tanto durante
el SSR como durante la navegación del lado del cliente. Un plugin .client carga el bundle solo
en el navegador.
Los pasos usan APIs que Nuxt 3 y Nuxt 4 comparten. En un proyecto Nuxt 4 con el directorio app/
por defecto, pon pages/ y plugins/ dentro de app/; server/ queda en la raíz del proyecto.
Configura Nuxt
// nuxt.config.ts
export default defineNuxtConfig({
css: ['@mapvx/web-components-ssr/dist/browser/styles.css'],
vue: {
compilerOptions: {
// Sin esto, Vue advierte sobre un componente desconocido y los listeners @userAction no se disparan
isCustomElement: (tag) => tag === 'mapvx-website',
},
},
runtimeConfig: {
// El navegador necesita ambos valores como atributos, así que son públicos.
// Defínelos con NUXT_PUBLIC_MAPVX_API_KEY y NUXT_PUBLIC_MAPVX_INSTITUTION_ID
public: {
mapvxApiKey: '',
mapvxInstitutionId: '',
},
},
})Usa runtimeConfig en lugar de process.env en los componentes: process.env está vacío en el
navegador, así que el servidor y el cliente renderizarían valores de atributo distintos.
Carga el bundle solo en el navegador
// plugins/mapvx.client.ts (el sufijo .client lo mantiene fuera del servidor)
export default defineNuxtPlugin(() => {
// Sin await, para que la hidratación no espere la descarga
import('@mapvx/web-components-ssr')
})Prepara los datos en una ruta de servidor
// server/api/mapvx-initial-data.get.ts
import { getMapvxInitialData } from '../utils/mapvx-initial-data'
export default defineEventHandler(async (event) => {
const { mapvxApiKey } = useRuntimeConfig(event).public
return { initialData: (await getMapvxInitialData(mapvxApiKey)) ?? null }
})server/utils/mapvx-initial-data.ts es el wrapper con tiempo de espera.
La ruta de servidor es necesaria porque useAsyncData también se ejecuta en el navegador durante
la navegación del lado del cliente: llamar al helper directamente dentro de él movería la llamada
al navegador. Para guardar el resultado en caché, defineCachedEventHandler de Nitro acepta un
maxAge.
Renderiza el elemento
<!-- pages/index.vue -->
<script setup lang="ts">
const config = useRuntimeConfig()
const { data } = await useAsyncData('mapvx-initial-data', () => $fetch('/api/mapvx-initial-data'))
</script>
<template>
<mapvx-website
:api-key="config.public.mapvxApiKey"
:institution-id="config.public.mapvxInstitutionId"
:initial-data="data?.initialData ?? undefined"
default-to-map="true"
/>
</template>SvelteKit
Una función load de servidor prepara los datos y se los pasa a la página, y la página importa el
bundle en onMount, que solo se ejecuta en el navegador.
Agrega las variables de entorno
# .env
MAPVX_API_KEY=YOUR_API_KEY
MAPVX_INSTITUTION_ID=YOUR_INSTITUTION_IDPrepara los datos en el servidor
// src/routes/+page.server.ts
import { MAPVX_API_KEY, MAPVX_INSTITUTION_ID } from '$env/static/private'
import { getMapvxInitialData } from '$lib/server/mapvx-initial-data'
import type { PageServerLoad } from './$types'
export const load: PageServerLoad = async () => ({
// El navegador necesita la API key como atributo api-key, así que devolverla está bien
apiKey: MAPVX_API_KEY,
institutionId: MAPVX_INSTITUTION_ID,
initialData: await getMapvxInitialData(MAPVX_API_KEY),
})src/lib/server/mapvx-initial-data.ts es el
wrapper con tiempo de espera. $env/static/private incrusta los
valores al compilar; usa $env/dynamic/private para leerlos en tiempo de ejecución. No leas
process.env en +page.svelte: no existe en el navegador.
Renderiza el elemento
<!-- src/routes/+page.svelte (Svelte 5) -->
<script lang="ts">
import { onMount } from 'svelte'
import '@mapvx/web-components-ssr/dist/browser/styles.css'
let { data } = $props()
// onMount solo se ejecuta en el navegador, después de la hidratación
onMount(() => {
import('@mapvx/web-components-ssr')
})
</script>
<mapvx-website
api-key={data.apiKey}
institution-id={data.institutionId}
initial-data={data.initialData}
default-to-map="true"
></mapvx-website>En Svelte 4, declara los datos con export let data en lugar de $props().
Angular SSR
La app anfitriona copia el bundle como asset y lo carga desde index.html, así que el servidor
nunca lo evalúa. El elemento incluye su propio Angular y funciona de forma independiente de la
versión de Angular y de la configuración de detección de cambios de tu app (por ejemplo, una app
Angular 20 con zone.js). El código de servidor de abajo usa PendingTasks.run y las rutas de
servidor con RenderMode, que requieren Angular 19 o posterior.
Copia el bundle como asset
En angular.json, agrega esta entrada a assets en las opciones de build:
{
"glob": "**/*",
"input": "node_modules/@mapvx/web-components-ssr/dist/browser",
"output": "mapvx-website/1.6.1"
}Conserva la versión en la carpeta de salida y cámbiala cuando actualices: el server.ts que
genera Angular CLI sirve los archivos estáticos con una caché de un año, y main.js mantiene su
nombre entre versiones.
Cárgalo en index.html
<!-- src/index.html, dentro de <head> -->
<link rel="stylesheet" href="mapvx-website/1.6.1/styles.css" />
<script type="module" src="mapvx-website/1.6.1/main.js"></script>No importes @mapvx/web-components-ssr desde tu código TypeScript salvo con import type: ese
código también se ejecuta en el servidor.
Renderiza el elemento con los datos del servidor
// src/app/mapvx.component.ts
import { isPlatformServer } from '@angular/common'
import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
PLATFORM_ID,
PendingTasks,
TransferState,
inject,
makeStateKey,
signal,
} from '@angular/core'
import { getMapvxInitialData } from './mapvx-initial-data'
const MAPVX_INITIAL_DATA = makeStateKey<string>('mapvx-initial-data')
@Component({
selector: 'app-mapvx',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<mapvx-website
[attr.api-key]="apiKey"
[attr.institution-id]="institutionId"
[attr.initial-data]="initialData() || null"
default-to-map="true"
></mapvx-website>
`,
})
export class MapvxComponent {
readonly apiKey = 'YOUR_API_KEY'
// Nunca enlaces institution-id a null o undefined: quitarlo lanza un error dentro del componente
readonly institutionId = 'YOUR_INSTITUTION_ID'
private readonly transferState = inject(TransferState)
// En el navegador, esto lee la cadena con la que renderizó el servidor
readonly initialData = signal(this.transferState.get(MAPVX_INITIAL_DATA, ''))
constructor() {
if (isPlatformServer(inject(PLATFORM_ID)) && !this.initialData()) {
// El servidor espera esta tarea antes de serializar la página
inject(PendingTasks).run(async () => {
const data = await getMapvxInitialData(this.apiKey)
if (data) {
this.transferState.set(MAPVX_INITIAL_DATA, data)
this.initialData.set(data)
}
})
}
}
}CUSTOM_ELEMENTS_SCHEMAes obligatorio: sin él, el compilador rechaza<mapvx-website>como elemento desconocido.- Los bindings
[attr.*]escriben los booleanos como"true"o"false", ynullquita el atributo. Eso está bien parainitial-data, pero nunca enlacesinstitution-idanulloundefined(quitar atributos). PendingTaskshace que el servidor espere los datos antes de serializar la página, yTransferStatele entrega la misma cadena al navegador, así que el atributo no cambia durante la hidratación.mapvx-initial-data.tses el wrapper con tiempo de espera.
Renderiza en el servidor para tener datos actualizados
// src/app/app.routes.server.ts
import { RenderMode, ServerRoute } from '@angular/ssr'
export const serverRoutes: ServerRoute[] = [
// RenderMode.Prerender dejaría los datos fijos al compilar
{ path: '**', renderMode: RenderMode.Server },
]Con RenderMode.Server, cada solicitud llama al helper, así que guarda su resultado en caché
(caché y vigencia de los datos).
Solo en el cliente (sin datos iniciales)
Todas las recetas anteriores también funcionan sin initial-data: quita el helper y el código de
datos del servidor, y conserva la carga solo en el navegador. En ese caso no necesitas
@mapvx/web-components-ssr-helpers. El componente solicita sus datos en el navegador, y las
tarjetas aparecen una sola vez, cuando responde la API.
| Framework | Cargar el bundle solo en el navegador |
|---|---|
| Cualquier página renderizada en el servidor | <script type="module"> que apunte a main.js (CDN o alojamiento propio) |
| Next.js | next/script con type="module" y la copia en public/ |
| Nuxt | Un plugin .client que llame a import('@mapvx/web-components-ssr') |
| SvelteKit | import('@mapvx/web-components-ssr') dentro de onMount |
| Angular SSR | <script type="module"> en src/index.html |
En Next.js, por ejemplo, basta con un Server Component. Conserva la copia en public/, el import
de los estilos y la declaración de TypeScript de la receta de Next.js:
// app/page.tsx
import Script from 'next/script'
export default function Page() {
return (
<>
<Script type="module" src="/mapvx-website/1.6.1/main.js" />
<mapvx-website
api-key={process.env.MAPVX_API_KEY ?? ''}
institution-id={process.env.MAPVX_INSTITUTION_ID ?? ''}
default-to-map="true"
/>
</>
)
}