Next.jsReactTypeScripti18nAWSS3CloudFront

Own Info App veröffentlicht — Minimale App mit Next.js (SSG) × S3 × CloudFront

Sloth255
Sloth255
·3 min read·558 words

App-Übersicht

Ich habe „Own Info App" veröffentlicht, eine kleine Webanwendung, mit der Sie schnell Netzwerkinformationen (z. B. Ihre globale IP-Adresse) sowie Browser-/Geräteinformationen überprüfen und sofort kopieren/teilen können.

https://x.com/Sloth255000/status/1986792593569763395
  • Quelltechnologie (Teilkonfiguration): Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS
  • Distribution: S3 + CloudFront (Static Export)
  • Mehrsprachig: Englisch/Japanisch/Chinesisch/Koreanisch/Deutsch/Französisch/Spanisch/Portugiesisch
  • Analytics: Firebase Analytics

Seitenübergänge und UI sind so einfach wie möglich gehalten. Einfach öffnen, die benötigten Informationen „kopieren" oder „teilen" und schließen – das ist alles.


Tech-Stack und Design-Punkte

1) Next.js 15 App Router × Static Export

  • Vollständig statische Ausgabe (SSG) mit output: 'export'
  • Ausgabedateien haben eine flache Struktur mit index.html, en.html, ja.html usw. direkt im out-Verzeichnis

Diese Konfiguration macht CDN-Distribution und Caching einfach und erzeugt eine schnelle App ohne dynamische Server oder SSR.

2) URL-Rewriting mit CloudFront Functions

Verwendung von CloudFront Functions (cloudfront-js-2.0) für leichtgewichtiges URL → Datei-Mapping.

  • //index.html
  • /ja/ja.html (Trailing Slash wird ähnlich behandelt)
  • /contact, /privacy, /licenses → jeweilige *.html-Dateien
  • URIs mit Erweiterungen (.svg, .js, .css, ...) werden ohne Rewriting durchgelassen
  • Nicht unterstützte Locales werden zu /en/_not-found weitergeleitet

3) i18n und Routing

  • Unterstützte Sprachen: en, ja, zh, ko, de, fr, es, pt
  • Übersetzungen getrennt in src/locales/*.json, SSG für jede Locale im App Router
  • Root / rendert die Standardsprache (en) äquivalent zu SSR für SEO & Bots, während bei Browser-Besuchen eine einmalige Client-Weiterleitung zur Locale-Seite basierend auf navigator.languages durchgeführt wird

4) SEO und OGP/Twitter Cards (pro Locale)

  • generateMetadata() für jede Seite implementiert, um „canonical / og:url / og:image / og:locale" pro Sprache korrekt auszugeben
  • OGP-Bilder (public/og-*.png) für jede Sprache vorbereitet, mit Beschreibungstext in Bildern aus Übersetzungstexten erstellt
  • Strukturierte Daten (JSON-LD) pro Locale in Server-Komponenten ausgegeben (application/ld+json). Alle 8 Sprachen explizit in inLanguage angegeben
// Beispiel: Ausgabe strukturierter Daten (Auszug)
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) Korrekte <html lang>-Angabe pro Locale

Im App Router gibt es die Einschränkung, dass „das <html> des Root-Layouts allen Seiten gemeinsam ist", was dazu führt, dass <html lang="en"> zum Build-Zeitpunkt bestehen bleibt.
Diesmal habe ich eine schlanke Komponente eingebaut, die „<html lang> clientseitig überschreibt" – sowohl im Locale-Layout als auch auf der Root-Seite –, um während der tatsächlichen Nutzung auf das korrekte Sprach-Tag zu aktualisieren.

'use client'
import { useEffect } from 'react'
export default function HtmlLangSetterClient({ lang }: { lang: string }) {
  useEffect(() => {
    document.documentElement.setAttribute('lang', lang)
  }, [lang])
  return null
}

Den Unterschied zwischen statischem HTML zur Build-Zeit und dem tatsächlich von Benutzern gesehenen DOM so gering wie möglich haltend, habe ich eine praktische Implementierung unter den Static-Export-Einschränkungen erreicht.


Deployment/Betrieb

Infrastruktur

  • S3 (statisches Hosting) + CloudFront (Origin ist S3s Website-Endpunkt)
  • CloudFront Functions separat deployed (SAM-Template für Ressourcenerstellung, Code-Updates auf CI-Seite)

CI/CD (GitHub Actions)

  • Ausgelöst durch Push auf main:
    • Next.js Build (Static Export)
    • S3-Sync
      • /_next/static mit max-age=31536000, immutable
      • HTML mit max-age=300, must-revalidate
    • CloudFront Function Code-Update (update-functionpublish-function)
    • CloudFront Invalidation (/*) bei Bedarf

AWS-Credentials, S3-Bucket, CloudFront Distribution ID und NEXT_PUBLIC_* sind als Secrets/Vars konfiguriert.
Manuelle Updates über CLI sind möglich, aber grundsätzlich dem CI überlassen.


Zusammenfassung

  • Lokalisierung
  • Minimales SEO
  • Minimale Infrastrukturkonfiguration
  • CI/CD & IaC

Als ich mit der persönlichen Entwicklung begann, wollte ich den gesamten Prozess der Veröffentlichung einer App durchlaufen – irgendetwas –, so wurde es diese Art von einfacher, kostengünstiger App.
Ich freue mich, dass ich zum ersten Mal Next.js ausprobieren konnte. Als Nächstes werde ich versuchen, auf Vercel mit SSR statt SSG zu deployen.

Schauen Sie sich die von mir erstellte App an 📱

https://own-info-app.sloth255.comhttps://own-info-app.sloth255.com