DEV Community

Cover image for Sin backend: cómo bigwords.page guarda todo en la URL
lu1tr0n
lu1tr0n

Posted on Originally published at elsolitario.org

Sin backend: cómo bigwords.page guarda todo en la URL

bigwords.page no tiene base de datos, no tiene registro de usuarios y no guarda un solo byte en un servidor: cada cartel, temporizador o contraseña de wifi que genera vive entero adentro del link que compartís. La clave está en el fragmento de la URL, la parte que va después del signo numeral (#), que el navegador nunca envía a ningún servidor. Este artículo explica paso a paso cómo funciona esa técnica y cómo podés usarla para construir tus propias herramientas sin backend, sin cuentas y 100% compartibles por link.

TL;DR

  • El navegador nunca envía el fragmento de la URL (todo lo que va después de #) a ningún servidor, según define el RFC 3986.- bigwords.page codifica texto, color, tipografía e intervalo de slides en una sola URL, sin backend ni base de datos.- window.location.hash y la History API permiten leer y actualizar ese estado sin recargar la página.- Un parámetro qr= genera códigos QR completos en el navegador, sin pedirle nada a un servidor externo.- Construir tu propia herramienta sin servidor toma menos de 40 líneas de JavaScript puro.

¿Qué es el fragmento de la URL?

El fragmento de la URL es la porción de un enlace que empieza con el signo numeral (#) y que, según el RFC 3986, el navegador procesa solo del lado del cliente: nunca viaja en la petición HTTP. bigwords.page aprovecha esa propiedad para guardar ahí todo el estado de la app: texto, color, tipografía e intervalo de las diapositivas.

Originalmente el fragmento servía para saltar a una sección dentro de la misma página, ese viejo truco de #introduccion que te lleva a un <h2 id='introduccion'>. La diferencia con la query string, la parte que arranca con ?, es clave. La query string sí se manda al servidor en cada petición y por eso aparece en los logs de acceso; el fragmento, en cambio, solo existe en la memoria del navegador.
El RFC 3986 define el fragmento de la URL desde 2005 y ningún navegador lo incluye en la petición HTTP.

Por qué importa: cero backend, cero cuentas

Cuando el estado completo de una app cabe en un link, no hace falta un servidor que lo guarde. bigwords.page es un archivo estático: HTML, CSS y JavaScript que podés servir desde cualquier CDN sin preocuparte por bases de datos, backups ni migraciones. El costo de infraestructura se acerca a cero porque no hay nada que escalar del lado del servidor. Cada visitante hace todo el trabajo en su propio navegador.

Tampoco hace falta pedir cuentas. Para guardar la contraseña de wifi de un café o el horario de un vuelo no necesitás que el usuario inicie sesión, porque el link ya contiene el dato. Esto simplifica el flujo completo: crear, editar y compartir son la misma acción de copiar una URL.

Hay un beneficio de privacidad casi accidental. Como el fragmento nunca sale del navegador, no queda registrado en ningún log de servidor ni en ninguna base de datos externa. Eso no significa que el dato sea secreto, porque cualquiera con el link lo lee igual, pero sí que nadie más que el navegador del usuario procesa ese contenido.

Esta misma lógica explica por qué equipos que organizan eventos o señalética temporal prefieren esta técnica en lugar de montar un panel de administración. Un cartel para una conferencia de un día no justifica pagar por un backend que va a usarse apenas unas horas, y alcanza con un link que cualquiera puede generar, editar y volver a compartir en segundos.

💡 Tip: si necesitás que un dato sea privado de verdad, el fragmento de la URL no alcanza: cualquiera con el link completo accede a todo. Para eso hace falta cifrado adicional, no solo omitir el servidor.

Cómo funciona por dentro

El esquema de bigwords.page reutiliza una sintaxis parecida a la de la query string, pero colocada después del #. Todo lo que va después del signo numeral cae dentro del fragmento de la URL, y el navegador lo procesa sin pedir nada a ningún servidor. El primer tramo, antes del primer &, es el mensaje; el resto son pares clave=valor separados por &, igual que en un formulario HTML clásico.

Como el mensaje puede contener espacios, saltos de línea o el propio signo numeral, todo pasa primero por percent-encoding: un espacio se escribe %20, un salto de línea %0A y un # literal, para escribir un encabezado con markdown, se escribe %23. bigwords.page eligió || como separador de diapositivas porque esa combinación de caracteres no aparece en ningún percent-encoding estándar, así que nunca choca por accidente con el contenido del mensaje.

El markdown que soporta el sitio es deliberadamente mínimo. Una función reemplaza cada **texto** por <strong>texto</strong> con una expresión regular, y cada %0A ya decodificado se convierte en un salto de línea real dentro del HTML. No hace falta un parser de markdown completo cuando el vocabulario que necesitás cubrir es tan chico.

El intervalo entre diapositivas (&interval=3) y las animaciones (&anim=pulse, &anim=scroll, &anim=rainbow) funcionan con el mismo principio: son clases CSS que el script activa según el valor leído del fragmento, con un setInterval() que rota el arreglo de slides cada N segundos. No hay nada en el servidor decidiendo cuándo cambiar de diapositiva, todo el temporizador vive en el navegador que tenés enfrente.

El caso más llamativo es el de las imágenes. El parámetro img= no apunta a un archivo en un servidor: recibe una data URI, es decir, la imagen entera codificada en base64 dentro del mismo link. Así, hasta el logo de un café para su cartel de wifi vive adentro de la URL, sin ningún archivo externo que pueda romperse o desaparecer.

flowchart TD
    A["URL completa"] --> B["Antes del #: se envia al servidor"]
    A --> C["Despues del #: fragmento, solo en el navegador"]
    B --> D[("Servidor: sirve HTML y JS estaticos")]
    C --> E["JavaScript local decodifica texto, color y tipografia"]
Enter fullscreen mode Exit fullscreen mode

La secuencia completa, de punta a punta, nunca incluye un segundo viaje al servidor para obtener el contenido del cartel:

sequenceDiagram
    participant U as Usuario
    participant S as Servidor
    participant N as Navegador
    U->>S: GET /index.html (sin fragmento)
    S-->>N: HTML y JS estaticos
    Note over N: El navegador lee el fragmento localmente
    N->>N: Decodifica mensaje, color y tipografia
    N-->>U: Renderiza el cartel completo
Enter fullscreen mode Exit fullscreen mode

Ejemplos prácticos

Antes de tocar nada de bigwords.page conviene separar las dos preguntas que resuelve la técnica: cómo leer el fragmento actual, y cómo convertirlo en algo que se pueda renderizar. Los siguientes fragmentos de código muestran el camino completo, del más simple al más cercano a lo que corre en producción.

console.log(window.location.hash);
// si la URL es https://ejemplo.com/#Hola%20Mundo
// la consola imprime: "#Hola%20Mundo"
Enter fullscreen mode Exit fullscreen mode

Notá que location.hash incluye el símbolo #. Para separar el mensaje de los parámetros hay que cortar la cadena en el primer & y decodificar cada parte por separado:

const raw = window.location.hash.slice(1); // quita el #
const [mensajeCrudo, ...resto] = raw.split('&');
const mensaje = decodeURIComponent(mensajeCrudo);
const params = new URLSearchParams(resto.join('&'));

console.log(mensaje);          // "Hola Mundo"
console.log(params.get('bg')); // "111111"
Enter fullscreen mode Exit fullscreen mode

Con mensaje y params ya separados, armar las diapositivas es cuestión de dividir por ||:

const slides = mensaje.split('||');
console.log(slides);
// ["Coffee", "Tea", "Water"]
// para la URL #Coffee||Tea||Water&interval=2
Enter fullscreen mode Exit fullscreen mode

Con esas piezas, separar el mensaje de los parámetros, decodificar y partir por ||, ya tenés la lógica completa que usa bigwords.page para pasar de una URL plana a un cartel interactivo. El resto es estilo: tipografías, colores y animaciones que se aplican sobre el mismo texto ya decodificado.

Cómo empezar: construí tu propio mini-bigwords

No necesitás instalar nada para probar esta técnica: un archivo .html y un navegador alcanzan. Si además querés abrir el link desde otro dispositivo en tu red local, para probarlo en un celular o una smart TV, instalá Node.js y corré npx serve . desde la carpeta del proyecto; el comando es idéntico en Windows, macOS y Linux.

Guardá este archivo como index.html:




    function render() {
      const raw = location.hash.slice(1);
      const [textoCrudo, ...resto] = raw.split('&');
      const params = new URLSearchParams(resto.join('&'));
      const texto = decodeURIComponent(textoCrudo || 'Hola%20Mundo').replace(/%0A/g, '\n');
      document.body.style.background = '#' + (params.get('bg') || '111111');
      const cartel = document.getElementById('cartel');
      cartel.style.color = '#' + (params.get('fg') || 'ffd60a');
      cartel.textContent = texto;
    }
    render();
    window.addEventListener('hashchange', render);


Enter fullscreen mode Exit fullscreen mode

Abrí el archivo en el navegador y agregá #Hola%20Mundo&bg=111111&fg=ffd60a al final de la URL en la barra de direcciones. El resultado esperado: fondo casi negro (#111111) con el texto "Hola Mundo" en amarillo (#ffd60a), centrado y ocupando toda la pantalla. Cambiá el fragmento sin recargar la página, por ejemplo a #Otro%20Mensaje, y el listener de hashchange actualiza el cartel al instante.
El formato WIFI:T:WPA;S:red;P:clave;; para codificar redes wifi en un código QR no necesita ningún servidor para generarse.

Casos de uso reales

La técnica rinde mejor cuando el contenido es temporal y el destino es una pantalla grande o un link que se comparte una sola vez. Algunos ejemplos documentados en el propio sitio:

  • Cartel de bienvenida: un mensaje con fondo y tipografía personalizados para recibir a alguien en el aeropuerto, listo para mostrarse en pantalla completa desde el celular.- Timer de examen o de pitch: una cuenta regresiva con {countdown} y un mensaje final distinto al llegar a cero, usando &timer=.- Contraseña de wifi en un café: el nombre de la red y la clave en texto grande, con un código QR generado en el navegador vía &qr= para que los clientes solo escaneen.- Letrero de sala o puerta de embarque: un cartel estático tipo "Gate 12: Boarding now" que cualquiera puede armar sin pedirle nada a un sistema de señalización corporativo.- Menú por código QR: un cartel minimalista que invita a escanear para ver el menú, apuntando a otra URL, sin imprimir nada nuevo cada vez que cambia un precio.- Agenda del día de un evento: varias diapositivas separadas por || con el cronograma, rotando cada pocos segundos en una pantalla a la entrada.

Errores comunes y buenas prácticas

El error más común es olvidar el percent-encoding: escribir un & o un # literal dentro del mensaje rompe el parseo, porque el navegador y el propio script los interpretan como separadores en lugar de como texto. La regla simple es usar siempre encodeURIComponent() antes de armar el link a mano.

Otro problema aparece al compartir el link a través de acortadores de URL: no todos preservan el fragmento completo en la redirección. Antes de compartir un link acortado con contenido crítico, una contraseña de wifi por ejemplo, conviene probarlo primero en una pestaña nueva.

También hay un límite práctico de longitud. No existe un tope fijo en el estándar, pero navegadores y lectores de QR sí imponen límites reales: un código QR de alta capacidad soporta unos 4.000 caracteres alfanuméricos en su versión más densa, así que un mensaje larguísimo con una imagen en base64 puede superar ese margen y dejar de ser escaneable.

Un tercer problema aparece al copiar y pegar un link a mano desde un documento de texto: algunos editores convierten el espacio codificado como %20 en un signo + si lo interpretan como parte de un formulario clásico, y bigwords.page no espera ese formato. Conviene copiar el link completo desde la barra de direcciones, nunca escribirlo de memoria.

También vale la pena probar el link en más de un canal antes de confiar en él para un evento en vivo: compartirlo por SMS, WhatsApp o un código QR impone límites de longitud distintos entre sí. Si el link final supera unos pocos miles de caracteres, por ejemplo por una imagen en base64 pesada, algunos de esos canales pueden truncarlo o rechazarlo antes de que llegue a destino.

⚠️ Ojo: el fragmento de la URL no es privado. Cualquiera que vea el link completo, en el historial del navegador, en una captura de pantalla o reenviado por chat, lee exactamente el mismo contenido que vos.

Comparativa con alternativas

Guardar estado en el cliente no es una idea nueva: la pregunta real es dónde conviene poner cada tipo de dato según quién necesita leerlo y por cuánto tiempo. La tabla compara el hash de la URL con las otras tres opciones más comunes en una app web.
OpciónCuándo usarlaVentajaLimitaciónFragmento de la URL (#)Contenido para compartir por link, sin cuentasNunca llega al servidor; cero backendNo es privado ni indexable por buscadoresQuery string (?)Parámetros que el servidor necesita leer (filtros, tracking)El servidor puede procesarlo y loguearloViaja en cada petición; aparece en logs de accesolocalStoragePreferencias de un único dispositivo, sin compartirPersiste entre sesiones sin tocar la URLNo viaja entre dispositivos ni se puede compartir por linkServidor + base de datosDatos que deben ser privados, editables por varios usuarios o consultablesControl total de acceso y consultasRequiere backend, cuentas y mantenimiento continuo

Profundizando

Actualizar el fragmento sin recargar la página ni ensuciar el historial de navegación tiene dos caminos. Asignar directamente location.hash = 'nuevo valor' dispara el evento hashchange y agrega una entrada al historial, el botón "atrás" vuelve al estado anterior. Si en cambio usás history.replaceState(null, '', '#nuevo valor'), de la History API, el fragmento cambia sin crear una entrada nueva, algo útil cuando el usuario arrastra un slider de color y no querés que cada movimiento quede en el historial.

Hay una limitación que vale la pena conocer antes de usar esta técnica para algo que necesite aparecer en redes sociales con preview: los bots que generan esas tarjetas (Telegram, WhatsApp, Slack) piden la URL al servidor sin ejecutar JavaScript, así que nunca ven el contenido que vive en el fragmento de la URL. Por eso bigwords.page funciona perfecto como herramienta personal o para compartir en un chat, pero no genera automáticamente un preview rico con el texto del cartel.

Para confirmar en la práctica que el fragmento nunca sale del navegador, abrí las herramientas de desarrollador, pestaña Red, y cargá cualquier link de bigwords.page: la petición GET que aparece en la lista termina en /, sin rastro de lo que va después del #. Si corrés un servidor HTTP propio en local y revisás su log de acceso, vas a ver exactamente lo mismo: el fragmento jamás figura ahí.

La misma idea de no mandarle al servidor lo que debería quedarse en el navegador aparece en otras herramientas conocidas. PrivateBin cifra un texto del lado del cliente y guarda en su servidor solo la versión cifrada; la clave de descifrado viaja en el fragmento, así que ni el operador del servicio puede leer el contenido sin el link completo. bigwords.page no cifra nada, porque el contenido no pretende ser secreto, pero comparte el mismo principio de diseño: todo lo sensible para la experiencia vive del lado del cliente.

Ese mismo diseño tiene un costo para el descubrimiento. Un buscador que indexa la web ve la URL raíz de bigwords.page, pero no cada cartel individual, porque el contenido de cada uno vive en un fragmento que varía por usuario y no forma parte del documento que el servidor entrega. Es la contrapartida lógica de no tener backend: lo que ganás en simplicidad de infraestructura lo perdés en que cada cartel sea, por diseño, invisible para un motor de búsqueda.

flowchart LR
    A["Mensaje tras el #"] --> B["%0A = salto de linea"]
    A --> C["%23 = almohadilla literal"]
    A --> D["doble barra separa slides"]
    B --> E["Parametros con &clave=valor"]
    C --> E
    D --> E
    E --> F["Navegador renderiza el cartel"]
Enter fullscreen mode Exit fullscreen mode

📖 Resumen en Telegram: Ver resumen

Tu próximo paso: copiá el HTML de la sección "Cómo empezar", abrilo en el navegador y agregale tu propio &anim=rainbow o &timer=30s hasta que entiendas cómo cada parámetro cambia el render.

Preguntas frecuentes

¿Qué diferencia hay entre el fragmento de la URL y la query string?

La query string, lo que va después de ?, se envía al servidor en cada petición HTTP y queda registrada en sus logs. El fragmento, lo que va después de #, el navegador lo procesa íntegramente del lado del cliente y nunca lo incluye en la petición.

¿Es seguro guardar una contraseña de wifi en el hash de la URL?

Es privado en el sentido de que ningún servidor lo registra, pero no es secreto: cualquiera con el link completo lee la contraseña igual que vos. Sirve para evitar que quede en una base de datos, no para ocultarlo de quien reciba el link.

¿bigwords.page funciona sin conexión a internet?

Una vez que el navegador descargó el HTML y el JavaScript del sitio, renderizar o editar el cartel no necesita ningún pedido adicional al servidor, porque todo el estado ya está en la URL y el parseo corre localmente.

¿Hay un límite de caracteres para el mensaje en la URL?

El estándar no fija un máximo, pero navegadores y, sobre todo, lectores de códigos QR sí imponen límites prácticos: una imagen en base64 incrustada vía img= puede empujar el link más allá de lo que un QR puede codificar de forma legible.

¿Puedo usar esta misma técnica fuera de bigwords.page?

Sí: cualquier app estática puede leer window.location.hash, parsear parámetros con URLSearchParams y renderizar en consecuencia. Es la misma idea detrás de varios editores online que guardan el código fuente en el hash de la URL para que el link sea autosuficiente.

Referencias

  • bigwords.page: sitio original y documentación de los parámetros de la URL.- MDN: Location.hash: referencia de la API que expone el fragmento de la URL en JavaScript.- RFC 3986: especificación del formato de URI, incluida la sección sobre el componente fragment.- MDN: History.replaceState(): cómo actualizar la URL sin agregar una entrada al historial.- PrivateBin: ejemplo real de otra herramienta que pone información sensible en el fragmento de la URL en lugar de en el servidor.

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Top comments (0)