Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,691 @@
|
||||
# International SEO & i18n
|
||||
|
||||
**Meta-Description:** Internationales SEO mit Next.js. hreflang Tags, URL-Strukturen, Content-Lokalisierung und Multi-Language Routing.
|
||||
|
||||
**Keywords:** International SEO, i18n, hreflang, Multilingual, Localization, Next.js i18n, URL Structure, Language Targeting
|
||||
|
||||
---
|
||||
|
||||
## Einführung
|
||||
|
||||
**Internationales SEO** erfordert mehr als nur Übersetzung. Richtige **hreflang-Implementation**, **URL-Strukturen** und **Lokalisierung** sind entscheidend. Dieser Guide zeigt Best Practices für mehrsprachige Websites mit Next.js 15.
|
||||
|
||||
---
|
||||
|
||||
## International SEO Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTERNATIONAL SEO ARCHITECTURE │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ URL Strategies: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. Subdirectories (Recommended): │ │
|
||||
│ │ example.com/de/ │ │
|
||||
│ │ example.com/en/ │ │
|
||||
│ │ example.com/fr/ │ │
|
||||
│ │ ✓ Easy to set up │ │
|
||||
│ │ ✓ Single domain authority │ │
|
||||
│ │ ✓ Clear language indication │ │
|
||||
│ │ │ │
|
||||
│ │ 2. Subdomains: │ │
|
||||
│ │ de.example.com │ │
|
||||
│ │ en.example.com │ │
|
||||
│ │ ✗ Separate domain authority │ │
|
||||
│ │ ✓ Easy geo-targeting │ │
|
||||
│ │ │ │
|
||||
│ │ 3. ccTLDs (Country-Specific): │ │
|
||||
│ │ example.de │ │
|
||||
│ │ example.co.uk │ │
|
||||
│ │ ✓ Strong geo-signal │ │
|
||||
│ │ ✗ Higher cost, separate SEO │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ hreflang Implementation: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ <link rel="alternate" hreflang="de" │ │
|
||||
│ │ href="https://example.com/de/page" /> │ │
|
||||
│ │ <link rel="alternate" hreflang="en" │ │
|
||||
│ │ href="https://example.com/en/page" /> │ │
|
||||
│ │ <link rel="alternate" hreflang="x-default" │ │
|
||||
│ │ href="https://example.com/page" /> │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Content Strategy: │
|
||||
│ ├── Translation (Basic) │
|
||||
│ ├── Localization (Cultural Adaptation) │
|
||||
│ └── Transcreation (Full Adaptation) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next.js i18n Setup
|
||||
|
||||
```typescript
|
||||
// next.config.js - i18n Configuration
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Built-in i18n (für Pages Router, App Router nutzt Middleware)
|
||||
// i18n: {
|
||||
// locales: ['de', 'en', 'fr', 'es'],
|
||||
// defaultLocale: 'de',
|
||||
// localeDetection: true
|
||||
// }
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
// middleware.ts - App Router i18n
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { match } from '@formatjs/intl-localematcher';
|
||||
import Negotiator from 'negotiator';
|
||||
|
||||
const locales = ['de', 'en', 'fr', 'es'];
|
||||
const defaultLocale = 'de';
|
||||
|
||||
function getLocale(request: NextRequest): string {
|
||||
// Check cookie first
|
||||
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
|
||||
if (cookieLocale && locales.includes(cookieLocale)) {
|
||||
return cookieLocale;
|
||||
}
|
||||
|
||||
// Then check Accept-Language header
|
||||
const headers = { 'accept-language': request.headers.get('accept-language') || '' };
|
||||
const languages = new Negotiator({ headers }).languages();
|
||||
|
||||
try {
|
||||
return match(languages, locales, defaultLocale);
|
||||
} catch {
|
||||
return defaultLocale;
|
||||
}
|
||||
}
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const pathname = request.nextUrl.pathname;
|
||||
|
||||
// Skip for static files and API routes
|
||||
if (
|
||||
pathname.startsWith('/_next') ||
|
||||
pathname.startsWith('/api') ||
|
||||
pathname.startsWith('/static') ||
|
||||
pathname.includes('.') // files with extensions
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Check if pathname has locale
|
||||
const pathnameHasLocale = locales.some(
|
||||
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
||||
);
|
||||
|
||||
if (pathnameHasLocale) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Redirect to locale
|
||||
const locale = getLocale(request);
|
||||
const newUrl = new URL(`/${locale}${pathname}`, request.url);
|
||||
|
||||
return NextResponse.redirect(newUrl);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!_next|api|static|.*\\..*).*)', '/']
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## App Router Structure
|
||||
|
||||
```typescript
|
||||
// app/[locale]/layout.tsx - Locale Layout
|
||||
import { notFound } from 'next/navigation';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getMessages, getTranslations } from 'next-intl/server';
|
||||
|
||||
const locales = ['de', 'en', 'fr', 'es'];
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map(locale => ({ locale }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
params: Promise<{ locale: string }>
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Metadata' });
|
||||
|
||||
return {
|
||||
title: {
|
||||
template: `%s | ${t('siteName')}`,
|
||||
default: t('siteName')
|
||||
},
|
||||
description: t('siteDescription'),
|
||||
alternates: {
|
||||
canonical: `https://example.com/${locale}`,
|
||||
languages: {
|
||||
'de': 'https://example.com/de',
|
||||
'en': 'https://example.com/en',
|
||||
'fr': 'https://example.com/fr',
|
||||
'es': 'https://example.com/es',
|
||||
'x-default': 'https://example.com'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!locales.includes(locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang={locale} dir={locale === 'ar' ? 'rtl' : 'ltr'}>
|
||||
<body>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// app/[locale]/page.tsx - Localized Page
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { Link } from '@/navigation';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ locale: string }>;
|
||||
}
|
||||
|
||||
export default async function HomePage({ params }: PageProps) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations('Home');
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1>{t('title')}</h1>
|
||||
<p>{t('description')}</p>
|
||||
|
||||
<Link href="/about">{t('aboutLink')}</Link>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## hreflang Implementation
|
||||
|
||||
```typescript
|
||||
// lib/i18n/hreflang.ts - hreflang Generator
|
||||
interface HreflangConfig {
|
||||
locales: string[];
|
||||
defaultLocale: string;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
interface HreflangLink {
|
||||
hrefLang: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export function generateHreflangLinks(
|
||||
path: string,
|
||||
config: HreflangConfig
|
||||
): HreflangLink[] {
|
||||
const { locales, defaultLocale, baseUrl } = config;
|
||||
|
||||
const links: HreflangLink[] = locales.map(locale => ({
|
||||
hrefLang: locale,
|
||||
href: `${baseUrl}/${locale}${path}`
|
||||
}));
|
||||
|
||||
// x-default für Sprachauswahl / Default
|
||||
links.push({
|
||||
hrefLang: 'x-default',
|
||||
href: `${baseUrl}/${defaultLocale}${path}`
|
||||
});
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
// components/Hreflang.tsx - Hreflang Component
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
const config: HreflangConfig = {
|
||||
locales: ['de', 'en', 'fr', 'es'],
|
||||
defaultLocale: 'de',
|
||||
baseUrl: 'https://example.com'
|
||||
};
|
||||
|
||||
export async function Hreflang() {
|
||||
const headersList = await headers();
|
||||
const pathname = headersList.get('x-pathname') || '/';
|
||||
|
||||
// Remove locale from pathname
|
||||
const path = pathname.replace(/^\/(de|en|fr|es)/, '');
|
||||
|
||||
const links = generateHreflangLinks(path, config);
|
||||
|
||||
return (
|
||||
<>
|
||||
{links.map(link => (
|
||||
<link
|
||||
key={link.hrefLang}
|
||||
rel="alternate"
|
||||
hrefLang={link.hrefLang}
|
||||
href={link.href}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage in layout.tsx
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
<Hreflang />
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// app/[locale]/[...slug]/page.tsx - Dynamic hreflang
|
||||
import { Metadata } from 'next';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ locale: string; slug: string[] }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const { locale, slug } = await params;
|
||||
const path = `/${slug.join('/')}`;
|
||||
|
||||
const locales = ['de', 'en', 'fr', 'es'];
|
||||
const baseUrl = 'https://example.com';
|
||||
|
||||
// Generate language alternates
|
||||
const languages: Record<string, string> = {};
|
||||
locales.forEach(loc => {
|
||||
languages[loc] = `${baseUrl}/${loc}${path}`;
|
||||
});
|
||||
languages['x-default'] = `${baseUrl}/de${path}`;
|
||||
|
||||
return {
|
||||
alternates: {
|
||||
canonical: `${baseUrl}/${locale}${path}`,
|
||||
languages
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translation Management
|
||||
|
||||
```typescript
|
||||
// messages/de.json
|
||||
{
|
||||
"Metadata": {
|
||||
"siteName": "Meine Website",
|
||||
"siteDescription": "Die beste Website für..."
|
||||
},
|
||||
"Home": {
|
||||
"title": "Willkommen",
|
||||
"description": "Entdecken Sie unsere Produkte und Dienstleistungen.",
|
||||
"aboutLink": "Über uns"
|
||||
},
|
||||
"Navigation": {
|
||||
"home": "Startseite",
|
||||
"about": "Über uns",
|
||||
"products": "Produkte",
|
||||
"contact": "Kontakt"
|
||||
},
|
||||
"Common": {
|
||||
"readMore": "Mehr lesen",
|
||||
"learnMore": "Mehr erfahren",
|
||||
"submit": "Absenden",
|
||||
"cancel": "Abbrechen"
|
||||
}
|
||||
}
|
||||
|
||||
// messages/en.json
|
||||
{
|
||||
"Metadata": {
|
||||
"siteName": "My Website",
|
||||
"siteDescription": "The best website for..."
|
||||
},
|
||||
"Home": {
|
||||
"title": "Welcome",
|
||||
"description": "Discover our products and services.",
|
||||
"aboutLink": "About us"
|
||||
},
|
||||
"Navigation": {
|
||||
"home": "Home",
|
||||
"about": "About",
|
||||
"products": "Products",
|
||||
"contact": "Contact"
|
||||
},
|
||||
"Common": {
|
||||
"readMore": "Read more",
|
||||
"learnMore": "Learn more",
|
||||
"submit": "Submit",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// lib/i18n/index.ts - Translation Utilities
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
|
||||
export default getRequestConfig(async ({ locale }) => ({
|
||||
messages: (await import(`@/messages/${locale}.json`)).default
|
||||
}));
|
||||
|
||||
// i18n.ts - Configuration
|
||||
export const locales = ['de', 'en', 'fr', 'es'] as const;
|
||||
export type Locale = (typeof locales)[number];
|
||||
|
||||
export const localeNames: Record<Locale, string> = {
|
||||
de: 'Deutsch',
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
es: 'Español'
|
||||
};
|
||||
|
||||
export const localeFlags: Record<Locale, string> = {
|
||||
de: '🇩🇪',
|
||||
en: '🇬🇧',
|
||||
fr: '🇫🇷',
|
||||
es: '🇪🇸'
|
||||
};
|
||||
|
||||
export const defaultLocale: Locale = 'de';
|
||||
|
||||
// Check if locale is valid
|
||||
export function isValidLocale(locale: string): locale is Locale {
|
||||
return locales.includes(locale as Locale);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Language Switcher
|
||||
|
||||
```tsx
|
||||
// components/LanguageSwitcher.tsx
|
||||
'use client';
|
||||
|
||||
import { useLocale } from 'next-intl';
|
||||
import { usePathname, useRouter } from '@/navigation';
|
||||
import { locales, localeNames, localeFlags, Locale } from '@/lib/i18n';
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const locale = useLocale();
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
const handleChange = (newLocale: Locale) => {
|
||||
// Set cookie for persistence
|
||||
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
|
||||
|
||||
// Navigate to new locale
|
||||
router.replace(pathname, { locale: newLocale });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<select
|
||||
value={locale}
|
||||
onChange={(e) => handleChange(e.target.value as Locale)}
|
||||
className="appearance-none bg-white border rounded-md px-4 py-2 pr-8"
|
||||
aria-label="Select language"
|
||||
>
|
||||
{locales.map((loc) => (
|
||||
<option key={loc} value={loc}>
|
||||
{localeFlags[loc]} {localeNames[loc]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Alternative: Link-based Switcher
|
||||
export function LanguageSwitcherLinks() {
|
||||
const locale = useLocale();
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<nav aria-label="Language selection">
|
||||
<ul className="flex gap-2">
|
||||
{locales.map((loc) => (
|
||||
<li key={loc}>
|
||||
<Link
|
||||
href={pathname}
|
||||
locale={loc}
|
||||
className={`px-3 py-1 rounded ${
|
||||
locale === loc
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-100 hover:bg-gray-200'
|
||||
}`}
|
||||
aria-current={locale === loc ? 'true' : undefined}
|
||||
>
|
||||
{localeFlags[loc]} {loc.toUpperCase()}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Localized Sitemap
|
||||
|
||||
```typescript
|
||||
// app/sitemap.ts - Multilingual Sitemap
|
||||
import { MetadataRoute } from 'next';
|
||||
import { locales, defaultLocale } from '@/lib/i18n';
|
||||
import { getAllPages } from '@/lib/content';
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const baseUrl = 'https://example.com';
|
||||
const pages = await getAllPages();
|
||||
|
||||
const sitemap: MetadataRoute.Sitemap = [];
|
||||
|
||||
// Static pages for each locale
|
||||
const staticPaths = ['', '/about', '/contact', '/products'];
|
||||
|
||||
for (const path of staticPaths) {
|
||||
for (const locale of locales) {
|
||||
const alternates: Record<string, string> = {};
|
||||
|
||||
locales.forEach(loc => {
|
||||
alternates[loc] = `${baseUrl}/${loc}${path}`;
|
||||
});
|
||||
alternates['x-default'] = `${baseUrl}/${defaultLocale}${path}`;
|
||||
|
||||
sitemap.push({
|
||||
url: `${baseUrl}/${locale}${path}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: path === '' ? 'daily' : 'weekly',
|
||||
priority: path === '' ? 1.0 : 0.8,
|
||||
alternates: {
|
||||
languages: alternates
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic pages (blog posts, products)
|
||||
for (const page of pages) {
|
||||
for (const locale of locales) {
|
||||
// Check if translation exists
|
||||
const hasTranslation = page.translations?.includes(locale);
|
||||
if (!hasTranslation && locale !== defaultLocale) continue;
|
||||
|
||||
const alternates: Record<string, string> = {};
|
||||
const availableLocales = page.translations || [defaultLocale];
|
||||
|
||||
availableLocales.forEach(loc => {
|
||||
alternates[loc] = `${baseUrl}/${loc}/${page.slug}`;
|
||||
});
|
||||
alternates['x-default'] = `${baseUrl}/${defaultLocale}/${page.slug}`;
|
||||
|
||||
sitemap.push({
|
||||
url: `${baseUrl}/${locale}/${page.slug}`,
|
||||
lastModified: new Date(page.updatedAt),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.7,
|
||||
alternates: {
|
||||
languages: alternates
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return sitemap;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SEO Best Practices
|
||||
|
||||
```typescript
|
||||
// lib/i18n/seo.ts - i18n SEO Helpers
|
||||
import { Metadata } from 'next';
|
||||
|
||||
interface LocalizedSEOConfig {
|
||||
title: Record<string, string>;
|
||||
description: Record<string, string>;
|
||||
keywords: Record<string, string[]>;
|
||||
ogImage?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function generateLocalizedMetadata(
|
||||
config: LocalizedSEOConfig,
|
||||
locale: string,
|
||||
path: string
|
||||
): Metadata {
|
||||
const baseUrl = 'https://example.com';
|
||||
const locales = ['de', 'en', 'fr', 'es'];
|
||||
|
||||
// Language alternates
|
||||
const languages: Record<string, string> = {};
|
||||
locales.forEach(loc => {
|
||||
languages[loc] = `${baseUrl}/${loc}${path}`;
|
||||
});
|
||||
languages['x-default'] = `${baseUrl}/de${path}`;
|
||||
|
||||
return {
|
||||
title: config.title[locale],
|
||||
description: config.description[locale],
|
||||
keywords: config.keywords[locale],
|
||||
|
||||
alternates: {
|
||||
canonical: `${baseUrl}/${locale}${path}`,
|
||||
languages
|
||||
},
|
||||
|
||||
openGraph: {
|
||||
title: config.title[locale],
|
||||
description: config.description[locale],
|
||||
url: `${baseUrl}/${locale}${path}`,
|
||||
siteName: locale === 'de' ? 'Meine Website' : 'My Website',
|
||||
locale: getOGLocale(locale),
|
||||
alternateLocales: locales
|
||||
.filter(l => l !== locale)
|
||||
.map(getOGLocale),
|
||||
images: config.ogImage?.[locale] ? [config.ogImage[locale]] : undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getOGLocale(locale: string): string {
|
||||
const map: Record<string, string> = {
|
||||
de: 'de_DE',
|
||||
en: 'en_US',
|
||||
fr: 'fr_FR',
|
||||
es: 'es_ES'
|
||||
};
|
||||
return map[locale] || 'en_US';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
| Aspect | Implementation | Priority |
|
||||
|--------|---------------|----------|
|
||||
| **URL Structure** | Subdirectories (/de/, /en/) | Critical |
|
||||
| **hreflang** | All pages, x-default | Critical |
|
||||
| **Canonical** | Per locale | Critical |
|
||||
| **Sitemap** | Multilingual with alternates | Important |
|
||||
| **Language Detection** | Accept-Language header | Important |
|
||||
| **Switcher** | Visible, accessible | Important |
|
||||
| **Content** | Fully translated, not auto | Important |
|
||||
| **Local Keywords** | Research per market | Important |
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
International SEO erfordert:
|
||||
|
||||
1. **Klare URL-Struktur**: Subdirectories für beste Balance
|
||||
2. **hreflang Tags**: Korrekt auf allen Seiten
|
||||
3. **Lokalisierung**: Mehr als nur Übersetzung
|
||||
4. **Technische Basis**: Sitemaps, Canonical, Meta
|
||||
|
||||
Internationales SEO öffnet globale Märkte.
|
||||
|
||||
---
|
||||
|
||||
## Bildprompts
|
||||
|
||||
1. "World map with language flags, multilingual website concept"
|
||||
2. "hreflang tag implementation diagram, connected language versions"
|
||||
3. "Language switcher UI designs, dropdown and flags"
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [Google i18n SEO Guide](https://developers.google.com/search/docs/specialty/international)
|
||||
- [Next.js Internationalization](https://nextjs.org/docs/app/building-your-application/routing/internationalization)
|
||||
- [next-intl Documentation](https://next-intl-docs.vercel.app/)
|
||||
- [Ahrefs International SEO](https://ahrefs.com/blog/international-seo/)
|
||||
Reference in New Issue
Block a user