1. i18n ルーティング
// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: ['/', '/(vi|en)/:path*'],
};
// i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['vi', 'en'],
defaultLocale: 'vi',
});
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
};
});
2. 翻訳ファイル
// messages/vi.json
{
"HomePage": {
"title": "Chào mừng đến với website",
"description": "Nền tảng học lập trình"
},
"Navigation": {
"home": "Trang chủ",
"blog": "Blog",
"about": "Giới thiệu"
}
}
// Sử dụng trong component
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
3. 動的メタデータ API
import type { Metadata } from 'next';
// Static metadata
export const metadata: Metadata = {
title: 'Trang chủ | xDev',
description: 'Nền tảng học lập trình',
openGraph: {
title: 'xDev',
description: 'Nền tảng học lập trình',
images: ['/images/og-image.jpg'],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'xDev',
description: 'Nền tảng học lập trình',
images: ['/images/og-image.jpg'],
},
};
// Dynamic metadata
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
images: [post.image],
type: 'article',
publishedTime: post.createdAt,
authors: [post.author.name],
},
};
}
4. JSON-LD構造化データ
export default async function PostPage({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.description,
image: post.image,
datePublished: post.createdAt,
author: {
'@type': 'Person',
name: post.author.name,
},
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>
<h1>{post.title}</h1>
</article>
</>
);
}
5. サイトマップとロボット
// app/sitemap.ts
import type { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await db.post.findMany({
where: { published: true },
select: { slug: true, updatedAt: true },
});
return [
{ url: 'https://blog.xdev.asia', lastModified: new Date(), priority: 1 },
{ url: 'https://blog.xdev.asia/blog', lastModified: new Date(), priority: 0.8 },
...posts.map(post => ({
url: `https://blog.xdev.asia/blog/${post.slug}`,
lastModified: post.updatedAt,
priority: 0.6,
})),
];
}
// app/robots.ts
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{ userAgent: '*', allow: '/', disallow: '/api/' },
],
sitemap: 'https://blog.xdev.asia/sitemap.xml',
};
}
次の記事: ファイルのアップロードと画像の最適化 — ファイル、次の画像、レスポンシブ画像をアップロードします。