Visão Geral do App
Lancei o "Own Info App", uma pequena aplicação web onde você pode verificar rapidamente informações de rede (como seu endereço IP global) e informações do navegador/dispositivo, copiar/compartilhar imediatamente.
https://x.com/Sloth255000/status/1986792593569763395- Stack (configuração parcial): Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS
- Distribuição: S3 + CloudFront (Static Export)
- Multilíngue: Inglês/Japonês/Chinês/Coreano/Alemão/Francês/Espanhol/Português
- Analytics: Firebase Analytics
As transições de página e a interface são mantidas o mais simples possível. Abra, "copie" ou "compartilhe" a informação que precisa, e feche — só isso.
Stack Tecnológica e Pontos de Design
1) Next.js 15 App Router × Static Export
- Saída totalmente estática (SSG) com
output: 'export' - Os arquivos de saída têm uma estrutura plana com
index.html,en.html,ja.html, etc. diretamente no diretório out
Essa configuração facilita a distribuição e o cache via CDN, criando um app ágil sem servidores dinâmicos ou SSR.
2) Reescrita de URL com CloudFront Functions
Usando CloudFront Functions (cloudfront-js-2.0) para mapeamento leve de URL → arquivo real.
/→/index.html/ja→/ja.html(trailing slash tratado similarmente)/contact,/privacy,/licenses→ arquivos*.htmlrespectivos- URIs com extensões (.svg, .js, .css, ...) passam sem reescrita
- Locales não suportados redirecionam para
/en/_not-found
3) i18n e Roteamento
- Idiomas suportados:
en, ja, zh, ko, de, fr, es, pt - Traduções separadas em
src/locales/*.json, SSG para cada locale no App Router - O caminho raiz
/renderiza o idioma padrão (en) equivalente ao SSR para SEO & bots, enquanto realiza um redirecionamento único no cliente para a página do locale com base emnavigator.languagesapenas para visitas via navegador
4) SEO e OGP/Twitter Cards (Por Locale)
- Implementado
generateMetadata()para cada página a fim de gerar corretamente "canonical / og:url / og:image / og:locale" por idioma - Imagens OGP (
public/og-*.png) preparadas para cada idioma, com texto de descrição nas imagens criadas a partir das traduções - Dados estruturados (JSON-LD) por locale em server components (
application/ld+json). Todos os 8 idiomas especificados explicitamente eminLanguage
// Exemplo: saída de dados estruturados (trecho)
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> Correto Por Locale
No App Router, existe uma restrição de que "o <html> do layout raiz é comum a todas as páginas", resultando em <html lang="en"> permanecendo em tempo de build.
Desta vez, incorporei um componente leve que "sobrescreve <html lang> no lado do cliente" tanto no layout de locale quanto na página raiz, atualizando para a tag de idioma correta durante o uso real.
'use client'
import { useEffect } from 'react'
export default function HtmlLangSetterClient({ lang }: { lang: string }) {
useEffect(() => {
document.documentElement.setAttribute('lang', lang)
}, [lang])
return null
}
Mantendo ao mínimo a diferença entre o HTML estático em tempo de build e o DOM que os usuários reais veem, alcancei uma implementação prática sob as restrições do Static Export.
Deploy/Operações
Infraestrutura
- S3 (hospedagem estática) + CloudFront (origem é o endpoint Website do S3)
- CloudFront Functions implantadas separadamente (template SAM para criação de recursos, atualizações de código pelo CI)
CI/CD (GitHub Actions)
- Acionado por push para
main:- Build do Next.js (Static Export)
- Sync com S3
/_next/staticcommax-age=31536000, immutable- HTML com
max-age=300, must-revalidate
- Atualização do código da CloudFront Function (
update-function→publish-function) - Invalidação do CloudFront (
/*) conforme necessário
Credenciais AWS, bucket S3, CloudFront Distribution ID e NEXT_PUBLIC_* são configurados como Secrets/Vars.
Atualizações manuais via CLI são possíveis, mas basicamente deixadas para o CI.
Resumo
- Localização
- SEO mínimo
- Configuração de infraestrutura mínima
- CI/CD & IaC
Ao começar o desenvolvimento pessoal, queria passar por todo o processo de lançar um app — qualquer coisa — então acabou sendo esse app simples e de baixo custo.
Fico feliz que pude experimentar o Next.js pela primeira vez. A seguir, vou tentar fazer deploy no Vercel com SSR em vez de SSG.
