Codificar una URL consiste en sustituir los caracteres que no pertenecen al conjunto seguro definido por el RFC 3986 por su secuencia `%XX` en hexadecimal, para que la URL viaje sin ambigüedad a través de navegadores, servidores y proxies. El error más habitual es aplicar `encodeURIComponent` a una URL completa (rompe `://` y las barras de la ruta) o, al contrario, usar `encodeURI` para un valor que va dentro de un parámetro de query (deja sin escapar el `&` y el `=`, mezclando el valor con la estructura de la query string). Este codificador y decodificador de URL aplica el algoritmo correcto según el contexto —una URL completa, un componente de ruta o un valor de query— directamente en el navegador.
Por qué existen los caracteres reservados y no reservados
El RFC 3986 divide los caracteres ASCII en reservados (tienen significado sintáctico dentro de la URL: : / ? # [ ] @ ! $ & ' ( ) * + , ; =) y no reservados (letras, dígitos, - _ . ~, que nunca necesitan codificarse). Cuando un carácter reservado forma parte del contenido de un valor —por ejemplo, un & dentro del texto de búsqueda de un usuario— y no de la estructura de la URL, debe codificarse como %26 para que el parser no lo confunda con el separador entre parámetros de la query string. Los caracteres fuera de ASCII (acentos, emoji, alfabetos no latinos) se codifican primero a UTF-8 y luego cada byte resultante se representa como %XX.
encodeURIComponent frente a encodeURI
encodeURIComponent codifica todos los caracteres reservados excepto - _ . ! ~ * ' ( ), y está pensado para un fragmento aislado: un valor de query, un segmento de ruta, un hash. encodeURI, en cambio, deja sin tocar los caracteres que forman la estructura de una URL completa (: / ? # & y similares) porque asume que le pasas la URL entera y no quieres romperla. El fallo clásico es usar encodeURI sobre un parámetro que el usuario ha escrito con un & o un =: como esos caracteres no se tocan, el navegador o el servidor los interpreta como parte de la estructura de la query string, no como contenido literal, y el parámetro llega truncado o corrupto al backend.
Codificación de la query string parámetro a parámetro
Una query string bien construida codifica cada clave y cada valor por separado con encodeURIComponent y luego los une manualmente con & y =, nunca al revés. Por ejemplo, para el valor precio: 10€ & envío gratis el resultado correcto es precio=10%E2%82%AC%20%26%20env%C3%ADo%20gratis (con el espacio como %20, el símbolo euro como su secuencia UTF-8 de tres bytes, y el & interno escapado para que no se confunda con el separador). Si en su lugar codificas la cadena precio=10€ & envío gratis entera con encodeURI, el = y el & internos sobreviven sin escapar y el parser de query string entiende dos parámetros donde debería haber uno solo.
El espacio: %20 frente a + según el contexto
En la parte de ruta y en fragmentos de una URL, el espacio se codifica como %20. Sin embargo, dentro de una query string con Content-Type: application/x-www-form-urlencoded (el formato que usan los formularios HTML clásicos y URLSearchParams), el espacio se representa históricamente como +, y un + literal debe entonces codificarse como %2B para no confundirse con un espacio. Esta es la razón por la que URLSearchParams de JavaScript produce + para los espacios mientras que encodeURIComponent produce %20: ambos son correctos, pero para contextos distintos, y mezclarlos en el mismo backend sin normalizar produce búsquedas o formularios que no encuentran coincidencias.
Doble codificación: un error silencioso y frecuente
Codificar dos veces una cadena que ya estaba codificada convierte cada % de una secuencia %XX en %2525, porque el propio carácter % es reservado y se codifica a %25. Esto ocurre con frecuencia en arquitecturas con varios saltos (un frontend que codifica, un proxy o CDN que reescribe la URL y la vuelve a codificar, y un backend que decodifica una sola vez): el resultado es que el backend recibe literalmente %25XX en lugar del carácter original y el dato llega corrupto. El síntoma característico en logs es encontrar secuencias %25 seguidas de dos caracteres hexadecimales donde se esperaba un carácter especial simple.
Emoji y caracteres multibyte en una URL
Un emoji como 🚀 no cabe en un solo punto de código ASCII: en UTF-8 ocupa cuatro bytes, y encodeURIComponent('🚀') produce %F0%9F%9A%80, la representación hexadecimal de esos cuatro bytes. Esto es distinto de un carácter acentuado como "ñ", que en UTF-8 ocupa dos bytes y se codifica como %C3%B1. Un error habitual es intentar decodificar una URL asumiendo Latin-1 en lugar de UTF-8: la secuencia de bytes es la misma, pero el resultado interpretado es una cadena de caracteres distinta y corrupta, típicamente visible como texto con acentos mal representados (mojibake).
Depurar una URL que llega rota al servidor
Cuando un backend recibe un parámetro truncado o con caracteres extraños, el primer paso es capturar la URL exacta que ha llegado (con curl -v o los logs del servidor web) y decodificarla manualmente segmento a segmento para localizar dónde se rompió la codificación. Si el problema aparece sólo con ciertos caracteres (&, =, #) casi siempre es un uso de encodeURI donde tocaba encodeURIComponent. Si aparece con acentos o emoji, revisa que todo el pipeline —frontend, proxy, backend— use UTF-8 de forma consistente, porque un solo eslabón con otra codificación (Latin-1, Windows-1252) corrompe el resultado aunque el resto de la cadena esté bien.
Salidas reales de ejemplo
Entrada: precio: 10€ & envío gratis
Salida: precio%3A%2010%E2%82%AC%20%26%20env%C3%ADo%20gratisEl símbolo € y el espacio se codifican; el resultado es seguro para insertarlo como valor de un único parámetro de query.
Entrada: https://ejemplo.com/buscar?q=café & tapas
encodeURI: https://ejemplo.com/buscar?q=caf%C3%A9%20&%20tapas
encodeURIComponent: https%3A%2F%2Fejemplo.com%2Fbuscar%3Fq%3Dcaf%C3%A9%20%26%20tapasencodeURI conserva los caracteres estructurales de una URL; encodeURIComponent los escapa todos porque asume un fragmento aislado.
GET /buscar?q=ni%2525C3%2525B1o%2520barato HTTP/1.1
# decodificar una vez -> q=ni%C3%B1o%20barato (todavía codificado)
# decodificar dos veces -> q=niño barato (valor correcto)El '%25' delante de cada par hexadecimal indica que la cadena se codificó dos veces antes de llegar al servidor.
Funciones de codificación de URL en JavaScript
| Función | Caracteres que NO codifica | Uso correcto |
|---|---|---|
| encodeURI | A-Z a-z 0-9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , # | Una URL completa que ya tiene su estructura definida |
| encodeURIComponent | A-Z a-z 0-9 - _ . ! ~ * ' ( ) | Un único valor: parámetro de query, segmento de ruta, hash |
| URLSearchParams | Igual que encodeURIComponent, pero usa + para el espacio | Construir o parsear query strings de formularios |
Cada función respeta un conjunto distinto de caracteres 'seguros' según el fragmento de URL al que se aplica.
Codificación de caracteres reservados frecuentes
| Carácter | Codificado | Significado si va sin codificar |
|---|---|---|
| espacio | %20 | Puede truncar la URL en algunos parsers antiguos |
| & | %26 | Separador entre parámetros de la query string |
| = | %3D | Separador entre clave y valor de un parámetro |
| # | %23 | Inicio del fragmento (ancla) de la URL |
| € | %E2%82%AC | Carácter no ASCII, tres bytes en UTF-8 |
Porcentaje hexadecimal resultante al aplicar encodeURIComponent a cada carácter.
Casos de uso comunes
- Construir a mano una URL de compartir en redes sociales con texto y hashtags dinámicos
- Depurar un parámetro de búsqueda que llega truncado al backend por un & sin codificar
- Generar enlaces de reseteo de contraseña con un token que puede contener caracteres especiales
- Insertar una URL completa como valor dentro de otro parámetro de query (redirect callbacks de OAuth)
- Convertir nombres de archivo con acentos o espacios a rutas seguras para servir desde un CDN
Buenas prácticas
- Usa `encodeURIComponent` para cualquier valor aislado y `encodeURI` sólo sobre una URL completa ya formada
- Construye la query string parámetro a parámetro, nunca codificando la cadena `clave=valor&clave2=valor2` entera
- No decodifiques una URL más veces de las que se codificó; comprueba si ves `%25` antes de decodificar de nuevo
- Fuerza UTF-8 de forma consistente en frontend, proxy y backend para evitar mojibake con acentos y emoji
- Si insertas una URL dentro de otra (redirect_uri de OAuth), codifica la URL interna completa como un único componente