Next.js

最適化

Next.js

Image・Font・Metadata・Bundle

Image と Font の最適化

next/image・next/font でCore Web Vitalsを改善

optimization.tsx tsx
import Image from 'next/image';
import { Inter, Noto_Sans_JP } from 'next/font/google';
import localFont from 'next/font/local';

// === next/image ===
// ✅ 自動的に WebP/AVIF に変換
// ✅ 遅延読み込み
// ✅ CLS 防止(サイズ予約)
// ✅ レスポンシブ対応

function Hero() {
    return (
        <>
            {/* 固定サイズ */}
            <Image src="/hero.jpg" alt="Hero" width={1200} height={630} priority />

            {/* fill(親要素のサイズに合わせる)*/}
            <div className="relative h-64">
                <Image src="/bg.jpg" alt="Background" fill className="object-cover" />
            </div>

            {/* 外部URL(next.config.js で許可が必要)*/}
            <Image src="https://cdn.example.com/img.jpg" alt="" width={400} height={300} />
        </>
    );
}

// === next/font(レイアウトシフトなし・プライバシー保護)===
const inter = Inter({
    subsets:  ['latin'],
    variable: '--font-inter',
    display:  'swap',
});

const notoSansJP = Noto_Sans_JP({
    weight:   ['400', '700'],
    subsets:  ['latin'],
    variable: '--font-noto',
});

const myFont = localFont({
    src: './fonts/MyFont.woff2',
    variable: '--font-my',
});

export default function Layout({ children }) {
    return (
        <html className={`${inter.variable} ${notoSansJP.variable}`}>
            <body>{children}</body>
        </html>
    );
}

Metadata と SEO

静的・動的メタデータ、OGP、sitemap

metadata.tsx tsx
import type { Metadata } from 'next';

// 静的メタデータ
export const metadata: Metadata = {
    title: {
        default:  'MyApp',
        template: '%s | MyApp', // 子ページのタイトルに付与
    },
    description: 'アプリの説明',
    keywords:    ['Next.js', 'React'],
    authors:     [{ name: 'Author' }],
    openGraph: {
        type:        'website',
        url:         'https://myapp.com',
        title:       'MyApp',
        description: 'アプリの説明',
        images:      [{ url: 'https://myapp.com/og.png', width: 1200, height: 630 }],
    },
    twitter: {
        card:    'summary_large_image',
        creator: '@handle',
    },
    robots: {
        index:  true,
        follow: true,
    },
};

// sitemap.ts
import type { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const posts = await getAllPosts();
    return [
        { url: 'https://myapp.com', lastModified: new Date(), changeFrequency: 'daily', priority: 1 },
        ...posts.map(p => ({
            url:             `https://myapp.com/blog/${p.slug}`,
            lastModified:    p.updatedAt,
            changeFrequency: 'weekly' as const,
            priority: 0.8,
        })),
    ];
}