Présentation de l'application
J'ai publié « Own Info App », une petite application web qui permet de vérifier rapidement les informations réseau (comme votre adresse IP publique) et les informations sur le navigateur/appareil, puis de les copier/partager immédiatement.
https://x.com/Sloth255000/status/1986792593569763395- Stack (configuration partielle) : Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS
- Distribution : S3 + CloudFront (Static Export)
- Multilingue : Anglais/Japonais/Chinois/Coréen/Allemand/Français/Espagnol/Portugais
- Analytics : Firebase Analytics
Les transitions de page et l'UI sont gardées aussi simples que possible. Il suffit de l'ouvrir, de « copier » ou « partager » les informations dont vous avez besoin, et de la fermer — c'est tout.
Stack technique et points de conception
1) Next.js 15 App Router × Static Export
- Sortie entièrement statique (SSG) avec
output: 'export' - Les fichiers de sortie ont une structure plate avec
index.html,en.html,ja.html, etc. directement dans le répertoire out
Cette configuration facilite la distribution CDN et la mise en cache, créant une application réactive sans serveurs dynamiques ni SSR.
2) Réécriture d'URL avec CloudFront Functions
Utilisation de CloudFront Functions (cloudfront-js-2.0) pour le mappage léger URL → fichier réel.
/→/index.html/ja→/ja.html(les trailing slashes sont gérés de la même façon)/contact,/privacy,/licenses→ fichiers*.htmlrespectifs- Les URIs avec extensions (.svg, .js, .css, ...) passent sans réécriture
- Les locales non supportées redirigent vers
/en/_not-found
3) i18n et routage
- Langues supportées :
en, ja, zh, ko, de, fr, es, pt - Traductions séparées dans
src/locales/*.json, SSG pour chaque locale dans App Router - La racine
/affiche la langue par défaut (en) équivalente au SSR pour le SEO & les bots, tout en effectuant une redirection client unique vers la page de locale basée surnavigator.languagesuniquement pour les visites via navigateur
4) SEO et OGP/Twitter Cards (par locale)
generateMetadata()implémenté pour chaque page afin de générer correctement « canonical / og:url / og:image / og:locale » par langue- Images OGP (
public/og-*.png) préparées pour chaque langue, avec le texte de description dans les images créé à partir des textes de traduction - Données structurées (JSON-LD) générées par locale dans les composants serveur (
application/ld+json). Les 8 langues explicitement spécifiées dansinLanguage
// Exemple : sortie de données structurées (extrait)
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> correct par locale
Dans App Router, il y a une contrainte selon laquelle « le <html> du layout racine est commun à toutes les pages », résultant en <html lang="en"> qui reste au moment du build.
Cette fois, j'ai incorporé un composant léger qui « écrase <html lang> côté client » dans le layout de la locale et la page racine, mettant à jour la balise de langue correcte lors de l'utilisation réelle.
'use client'
import { useEffect } from 'react'
export default function HtmlLangSetterClient({ lang }: { lang: string }) {
useEffect(() => {
document.documentElement.setAttribute('lang', lang)
}, [lang])
return null
}
Tout en maintenant la différence entre le HTML statique au moment du build et le DOM que les utilisateurs voient réellement à un minimum, j'ai réalisé une implémentation pratique sous les contraintes du Static Export.
Déploiement/Opérations
Infrastructure
- S3 (hébergement statique) + CloudFront (l'origine est l'endpoint Website de S3)
- CloudFront Functions déployées séparément (template SAM pour la création des ressources, mises à jour du code côté CI)
CI/CD (GitHub Actions)
- Déclenché par un push sur
main:- Build Next.js (Static Export)
- Synchronisation S3
/_next/staticavecmax-age=31536000, immutable- HTML avec
max-age=300, must-revalidate
- Mise à jour du code CloudFront Function (
update-function→publish-function) - Invalidation CloudFront (
/*) si nécessaire
Les identifiants AWS, le bucket S3, l'ID de distribution CloudFront et les NEXT_PUBLIC_* sont configurés en tant que Secrets/Vars.
Les mises à jour manuelles via CLI sont possibles, mais laissées au CI par défaut.
Résumé
- Localisation
- SEO minimal
- Configuration d'infrastructure minimale
- CI/CD & IaC
En commençant le développement personnel, je voulais parcourir l'ensemble du processus de publication d'une application — n'importe laquelle — alors c'est devenu ce genre d'application simple et peu coûteuse.
Je suis content d'avoir pu essayer Next.js pour la première fois. Ensuite, j'essaierai de déployer sur Vercel avec SSR au lieu de SSG.
