Descripción de la aplicación
He lanzado "Own Info App", una pequeña aplicación web donde puedes consultar rápidamente información de red (como tu dirección IP global) e información del navegador/dispositivo, y luego copiarla/compartirla de inmediato.
https://x.com/Sloth255000/status/1986792593569763395- Fuente (configuración parcial): Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS
- Distribución: S3 + CloudFront (Static Export)
- Multilingüe: Inglés/Japonés/Chino/Coreano/Alemán/Francés/Español/Portugués
- Analytics: Firebase Analytics
Las transiciones de página y la UI se mantienen lo más simples posible. Solo ábrela, "copia" o "comparte" la información que necesites, y ciérrala—eso es todo.
Stack tecnológico y puntos de diseño
1) Next.js 15 App Router × Static Export
- Salida completamente estática (SSG) con
output: 'export' - Los archivos de salida tienen una estructura plana con
index.html,en.html,ja.html, etc. directamente en el directorio out
Esta configuración facilita la distribución y el caché en CDN, creando una aplicación ágil sin servidores dinámicos ni SSR.
2) Reescritura de URL con CloudFront Functions
Usando CloudFront Functions (cloudfront-js-2.0) para el mapeo ligero de URL → archivo real.
/→/index.html/ja→/ja.html(trailing slash manejado similarmente)/contact,/privacy,/licenses→ archivos*.htmlrespectivos- URIs con extensiones (.svg, .js, .css, ...) pasan sin reescritura
- Los locales no soportados redirigen a
/en/_not-found
3) i18n y Routing
- Idiomas soportados:
en, ja, zh, ko, de, fr, es, pt - Traducciones separadas en
src/locales/*.json, SSG para cada locale en App Router - La raíz
/renderiza el idioma predeterminado (en) equivalente a SSR para SEO & bots, mientras realiza una redirección de cliente única a la página del locale basada ennavigator.languagessolo para visitas del navegador
4) SEO y OGP/Twitter Cards (por locale)
- Se implementó
generateMetadata()para cada página para generar correctamente "canonical / og:url / og:image / og:locale" por idioma - Imágenes OGP (
public/og-*.png) preparadas para cada idioma, con texto de descripción en imágenes creado desde textos de traducción - Datos estructurados (JSON-LD) generados por locale en componentes de servidor (
application/ld+json). Los 8 idiomas especificados explícitamente eninLanguage
// Ejemplo: Salida de datos estructurados (extracto)
export default function StructuredData({ title, description, siteUrl }: Props) {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'WebApplication',
name: title,
description,
url: siteUrl || '/',
applicationCategory: 'UtilityApplication',
operatingSystem: 'Web Browser',
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
inLanguage: [
{ '@type': 'Language', name: 'English', alternateName: 'en' },
{ '@type': 'Language', name: 'Japanese', alternateName: 'ja' },
{ '@type': 'Language', name: 'Chinese', alternateName: 'zh' },
{ '@type': 'Language', name: 'Korean', alternateName: 'ko' },
{ '@type': 'Language', name: 'German', alternateName: 'de' },
{ '@type': 'Language', name: 'French', alternateName: 'fr' },
{ '@type': 'Language', name: 'Spanish', alternateName: 'es' },
{ '@type': 'Language', name: 'Portuguese', alternateName: 'pt' },
],
};
return <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />;
}
5) <html lang> correcto por locale
En App Router, existe la restricción de que "el <html> del layout raíz es común a todas las páginas", lo que resulta en que <html lang="en"> permanece en el momento de la compilación.
Esta vez, incorporé un componente delgado que "sobreescribe <html lang> en el lado del cliente" tanto en el layout del locale como en la página raíz, actualizando al tag de idioma correcto durante el uso real.
'use client'
import { useEffect } from 'react'
export default function HtmlLangSetterClient({ lang }: { lang: string }) {
useEffect(() => {
document.documentElement.setAttribute('lang', lang)
}, [lang])
return null
}
Manteniendo la diferencia entre el HTML estático en tiempo de compilación y el DOM que ven los usuarios reales al mínimo, logré una implementación práctica bajo las restricciones de Static Export.
Despliegue/Operaciones
Infraestructura
- S3 (alojamiento estático) + CloudFront (el origen es el endpoint de sitio web de S3)
- CloudFront Functions desplegadas por separado (plantilla SAM para la creación de recursos, actualizaciones de código en el lado de CI)
CI/CD (GitHub Actions)
- Activado por push a
main:- Build de Next.js (Static Export)
- Sincronización con S3
/_next/staticconmax-age=31536000, immutable- HTML con
max-age=300, must-revalidate
- Actualización del código de CloudFront Function (
update-function→publish-function) - Invalidación de CloudFront (
/*) según sea necesario
Las credenciales de AWS, el bucket S3, el ID de distribución de CloudFront y NEXT_PUBLIC_* se configuran como Secrets/Vars.
Las actualizaciones manuales vía CLI son posibles, pero básicamente se dejan a CI.
Resumen
- Localización
- SEO mínimo
- Configuración de infraestructura mínima
- CI/CD & IaC
Al comenzar el desarrollo personal, quería pasar por todo el proceso de lanzar una aplicación—cualquier cosa—así que se convirtió en esta aplicación simple y de bajo costo.
Me alegra haber tenido la oportunidad de usar Next.js por primera vez. A continuación, intentaré desplegar en Vercel con SSR en lugar de SSG.
