Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

Next.jsとTypeScriptで作る高速SSGブログ:初心者でも簡単に実装できる完全ガイド

記事のサムネイル

Next.jsとTypeScriptでSSGブログを作る利点

Next.jsとTypeScriptを組み合わせてSSG(Static Site Generation)ブログを構築することには多くの利点があります。従来のWordPressなどのCMSと比較して、モダンな開発体験と優れたパフォーマンスを両立できるのが大きな魅力です。

型安全性による開発効率の向上

TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

TypeScriptの最大の強みは型安全性です。ブログシステムの開発では、記事データの構造や関数の入出力を明確に定義できるため、多くのバグを未然に防ぐことができます。

// 記事データの型定義例
interface Post {
  slug: string;
  title: string;
  description: string;
  date: string;
  content: string;
  tags: string[];
  thumbnail?: string;
}

// 型安全な関数の例
function getPostBySlug(slug: string): Post | undefined {
  // スラッグから記事を取得する処理
  return posts.find(post => post.slug === slug);
}

このように型を定義することで、エディタの自動補完が効き、誤った使い方をした場合はコンパイル時にエラーが表示されるため、開発効率が格段に向上します。

SSGによる高速なページ読み込みとSEO対策

Next.jsのSSG機能を使うと、ビルド時にHTMLが生成されるため、ユーザーがアクセスした際の読み込み速度が非常に速くなります。これはユーザー体験の向上だけでなく、Googleなどの検索エンジンが重視する指標にも良い影響を与えます。

// pages/index.tsx
// 静的サイト生成の例
export async function getStaticProps() {
  const allPosts = getAllPosts();
  return {
    props: {
      posts: allPosts,
    },
  };
}

export default function Home({ posts }: { posts: Post[] }) {
  return (
    <div>
      <h1>ブログ記事一覧</h1>
      <div>
        {posts.map((post) => (
          <PostCard key={post.slug} post={post} />
        ))}
      </div>
    </div>
  );
}

柔軟なコンテンツ管理とGitベースのワークフロー

Markdownファイルでコンテンツを管理することで、データベースを必要とせず、Gitで記事の変更履歴を管理できます。これにより、複数人での協力や、変更の追跡が容易になります。

// ファイルシステムからMarkdownファイルを読み込む例
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');

export function getAllPosts(): Post[] {
  const fileNames = fs.readdirSync(postsDirectory);
  const allPosts = fileNames.map((fileName) => {
    const slug = fileName.replace(/\.md$/, '');
    const fullPath = path.join(postsDirectory, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf8');
    const { data, content } = matter(fileContents);
    
    return {
      slug,
      title: data.title,
      description: data.description,
      date: data.date,
      content,
      tags: data.tags || [],
      thumbnail: data.thumbnail,
    };
  });

  // 日付でソート
  return allPosts.sort((a, b) => (a.date > b.date ? -1 : 1));
}

コスト効率の良いホスティング

静的ファイルのみで構成されるため、VercelやNetlifyなどのプラットフォームを利用すれば、無料または低コストでホスティングが可能です。また、CDNによる配信により、世界中のユーザーに高速なアクセスを提供できます。

APIルートによるサーバーレス機能の実装

完全に静的なサイトでありながら、必要に応じてAPIルートを使ってサーバーレス関数を実装できるのもNext.jsの強みです。

// pages/api/newsletter.ts
// ニュースレター登録API例
import type { NextApiRequest, NextApiResponse } from 'next';

type Data = {
  success: boolean;
  message: string;
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ success: false, message: 'Method not allowed' });
  }

  const { email } = req.body;

  try {
    // 実際にはここでニュースレターサービスのAPIを呼び出す
    // await subscribeToNewsletter(email);
    
    return res.status(200).json({ success: true, message: '登録が完了しました' });
  } catch (error) {
    return res.status(500).json({ success: false, message: 'エラーが発生しました' });
  }
}

これらの利点を活かすことで、速度、SEO、開発効率の高いブログサイトを構築することができます。特に技術ブログなどコードを多く含むコンテンツの場合、TypeScriptとNext.jsの組み合わせは非常に相性が良いと言えるでしょう。

プロジェクトのセットアップと基本構成

Next.jsとTypeScriptでのプロジェクトセットアップは非常に簡単です。初心者でも問題なく始められるよう、ステップバイステップで解説します。

create-next-appでプロジェクトの作成

まず、Node.jsとnpmがインストールされていることを確認してください。Next.jsプロジェクトは以下のコマンドで簡単に作成できます。

# Next.js + TypeScriptプロジェクトの作成
npx create-next-app@latest my-blog --typescript
cd my-blog

実行すると、いくつかの質問が表示されます:

Would you like to use ESLint? … YesWould you like to use Tailwind CSS? … YesWould you like to use `src/` directory? … NoWould you like to use App Router? (recommended) … NoWould you like to customize the default import alias? … No

ここではブログサイトを構築するためにPages Routerを選択し、TailwindCSSを使用することにします。

フォルダ構造の整備

Next.jsの基本的なフォルダ構造に加えて、SSGブログに必要なディレクトリを追加します。

mkdir -p posts components/layout components/post lib/types

これで以下のようなフォルダ構造が出来上がります:

my-blog/
├── components/       # 再利用可能なコンポーネント
│   ├── layout/       # レイアウト関連コンポーネント
│   └── post/         # 記事表示関連コンポーネント
├── lib/              # ユーティリティ関数
│   └── types/        # TypeScript型定義
├── node_modules/     # 依存パッケージ
├── pages/            # ページコンポーネント
├── posts/            # Markdownファイル(記事)
├── public/           # 静的ファイル
├── styles/           # CSSファイル
├── .eslintrc.json    # ESLint設定
├── next.config.js    # Next.js設定
├── package.json      # プロジェクト設定
└── tsconfig.json     # TypeScript設定

必要なパッケージのインストール

SSGブログを構築するために必要なパッケージをインストールします。

# Markdownパース用パッケージ
npm install gray-matter remark remark-html

# 日付フォーマット用
npm install date-fns

# シンタックスハイライト
npm install prismjs

package.jsonの確認と設定

package.jsonファイルには、プロジェクトの依存関係とスクリプトが記載されています。以下のようになっているか確認しましょう。

{
  "name": "my-blog",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "date-fns": "^2.30.0",
    "gray-matter": "^4.0.3",
    "next": "13.4.19",
    "prismjs": "^1.29.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "remark": "^14.0.3",
    "remark-html": "^15.0.2",
    "tailwindcss": "^3.3.3",
    "typescript": "5.1.6"
  },
  "devDependencies": {
    "@types/node": "^20.5.7",
    "@types/prismjs": "^1.26.0",
    "@types/react": "^18.2.21",
    "eslint": "8.48.0",
    "eslint-config-next": "13.4.19"
  }
}

TypeScriptの基本設定とNext.jsとの統合

TypeScriptとNext.jsを組み合わせる際の設定について詳しく見ていきましょう。tsconfig.jsonファイルは、TypeScriptの挙動を制御する重要なファイルです。

tsconfig.jsonの主要設定

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

主な設定項目の説明:

  • "target": "es5" - コンパイル後のJavaScriptバージョン
  • "strict": true - 厳格な型チェックを有効化
  • "jsx": "preserve" - JSXの変換をNext.jsに任せる
  • "baseUrl": ".""paths": {"@/*": ["./*"]} - インポートのパスエイリアスを設定

パスエイリアスをより便利に設定する

ディレクトリ階層が深くなってきたときに、相対パスでのインポートは煩雑になります。以下のようにパスエイリアスを設定するとコードがより整理されます。

"paths": {
  "@/components/*": ["components/*"],
  "@/lib/*": ["lib/*"],
  "@/styles/*": ["styles/*"],
  "@/types/*": ["lib/types/*"]
}

これにより、以下のようにインポートが簡潔になります:

// 改善前
import PostCard from '../../../components/post/PostCard';

// 改善後
import PostCard from '@/components/post/PostCard';

ESLintとPrettierの統合

コード品質を維持するために、ESLintとPrettierを設定しましょう。Next.jsはデフォルトでESLintをサポートしていますが、Prettierを追加することでコードの整形も自動化できます。

npm install -D prettier eslint-config-prettier

.eslintrc.jsonを以下のように設定します:

{
  "extends": [
    "next/core-web-vitals",
    "prettier"
  ],
  "rules": {
    "react/no-unescaped-entities": "off"
  }
}

.prettierrcファイルを作成します:

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}

package.jsonにスクリプトを追加しておくと便利です:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint",
  "format": "prettier --write ."
}

Next.js固有の型定義

Next.jsは、フレームワーク特有の関数やコンポーネントに対する型定義を提供しています。特に重要なのは以下の型です:

// ページコンポーネントの型
import type { NextPage } from 'next';

// データ取得関数の型
import type { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next';

// _app.tsxで使用するAppPropsの型
import type { AppProps } from 'next/app';

例えば、動的ルーティングを使用した記事ページでは以下のように型を活用できます:

import { GetStaticPaths, GetStaticProps, NextPage } from 'next';
import { ParsedUrlQuery } from 'querystring';

interface PostParams extends ParsedUrlQuery {
  slug: string;
}

const PostPage: NextPage<{ post: Post }> = ({ post }) => {
  // コンポーネントの実装
};

export const getStaticProps: GetStaticProps<{ post: Post }, PostParams> = async ({ params }) => {
  const post = getPostBySlug(params!.slug);
  // ...
  return { props: { post } };
};

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = getAllPosts();
  return {
    paths: posts.map((post) => ({
      params: { slug: post.slug },
    })),
    fallback: false,
  };
};

export default PostPage;

このように型を活用することで、開発時のエラーを早期に発見でき、また自動補完も効くようになり開発効率が向上します。

Markdownファイル管理とフロントマターの実装

SSGブログの記事データはMarkdownファイルで管理すると効率的です。ファイルの先頭にYAML形式でメタデータ(フロントマター)を記述し、gray-matterライブラリを使って解析します。

---
title: "Next.jsとTypeScriptでブログを作る方法"
description: "SSGブログの作り方を解説します"
date: "2025-05-01"
tags: ["Next.js", "TypeScript"]
---

ここから記事の本文が始まります...

gray-matterを使った解析コード例:

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');

export function getPostBySlug(slug: string) {
  const fullPath = path.join(postsDirectory, `${slug}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');
  const { data, content } = matter(fileContents);
  
  return {
    slug,
    title: data.title,
    description: data.description,
    date: data.date,
    content,
    tags: data.tags || [],
  };
}

Markdownパーシングとブログ機能の実装

Markdownファイルをパースして表示するには、remarkとrehypeのライブラリを活用します。以下は実装の一例です。

// lib/markdown.ts
import { remark } from 'remark';
import html from 'remark-html';
import prism from 'remark-prism';

export async function markdownToHtml(markdown: string) {
  const result = await remark()
    .use(html, { sanitize: false })
    .use(prism)
    .process(markdown);
  return result.toString();
}

記事ページを実装するためのNext.jsの動的ルーティングと型安全な実装は以下のようになります。

// pages/posts/[slug].tsx
import { GetStaticPaths, GetStaticProps } from 'next';
import { getAllPosts, getPostBySlug } from '@/lib/posts';
import { markdownToHtml } from '@/lib/markdown';

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const post = getPostBySlug(params?.slug as string);
  const content = await markdownToHtml(post.content);
  
  return {
    props: {
      post: {
        ...post,
        content,
      },
    },
  };
};

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = getAllPosts();
  
  return {
    paths: posts.map((post) => ({
      params: { slug: post.slug },
    })),
    fallback: false,
  };
};

SEO対策とパフォーマンス最適化

SEO対策には、Next.jsのHeadコンポーネントを使用したメタタグの設定が効果的です。また、画像最適化にはNext.jsのImageコンポーネントを使用します。

// components/SEO.tsx
import Head from 'next/head';

interface SEOProps {
  title: string;
  description: string;
  ogImage?: string;
}

export default function SEO({ title, description, ogImage }: SEOProps) {
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
  
  return (
    <Head>
      <title>{title}</title>
      <meta name="description" content={description} />
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      {ogImage && <meta property="og:image" content={`${siteUrl}${ogImage}`} />}
      <meta name="twitter:card" content="summary_large_image" />
    </Head>
  );
}

デプロイと運用保守

Next.jsブログは、Vercelに簡単にデプロイできます。以下のステップで実行します。

  1. GitHubリポジトリにプロジェクトをプッシュ
  2. Vercelアカウントを作成し、リポジトリを接続
  3. 環境変数を設定(必要に応じて)
  4. デプロイボタンをクリック

Vercelは継続的デプロイをサポートしているため、GitHubにプッシュするたびに自動的にデプロイが実行されます。

まとめ:あなただけのSSGブログを育てていくために

この記事では、Next.jsとTypeScriptを使ったSSGブログの作り方を初心者向けに解説しました。プロジェクトのセットアップから始まり、Markdownパーシングの実装、SEO対策、デプロイまでの一連の流れを学びました。このような技術スタックを選択することで、高速でSEOに強く、開発効率の良いブログサイトを構築することができます。

これらの基礎を理解した上で、あなた自身のアイデアを取り入れたオリジナルのブログサイトを作成し、さらに機能を拡張していってください。継続的な学習と改善を重ねることで、より質の高いWebサイトを構築する力が身につきます。最後に、Next.jsとTypeScriptの公式ドキュメントやコミュニティリソースを活用して、最新の情報をキャッチアップすることも忘れないでください。あなたのWeb開発の旅が実り多いものになることを願っています。すべての記事を取得する関数

ブログの記事一覧ページなどで使用するために、すべての記事を取得する関数を実装します。

// lib/posts.ts(続き)
export function getAllPosts(): Post[] {
  // 全Markdownファイルを取得
  const fileNames = fs.readdirSync(postsDirectory);
  const allPosts = fileNames
    .filter((fileName) => fileName.endsWith('.md'))
    .map((fileName) => {
      // ファイル名から.mdを除去してスラッグを取得
      const slug = fileName.replace(/\.md$/, '');
      // 記事データを取得
      return getPostBySlug(slug);
    });
  
  // 公開日の降順でソート
  return allPosts.sort((a, b) => {
    if (a.date < b.date) {
      return 1;
    } else {
      return -1;
    }
  });
}

タグによる記事のフィルタリング

タグページを実装するために、特定のタグを持つ記事だけを抽出する関数も用意しておくと便利です。

// lib/posts.ts(続き)
export function getPostsByTag(tag: string): Post[] {
  const allPosts = getAllPosts();
  return allPosts.filter((post) => post.tags.includes(tag));
}

// 全タグのリストを取得
export function getAllTags(): string[] {
  const posts = getAllPosts();
  const tags = new Set<string>();
  
  posts.forEach((post) => {
    post.tags.forEach((tag) => {
      tags.add(tag);
    });
  });
  
  return Array.from(tags);
}

日付の整形

記事の日付表示をフォーマットするために、date-fnsライブラリを活用します。

// components/date.tsx
import { format, parseISO } from 'date-fns';
import { ja } from 'date-fns/locale';

type DateProps = {
  dateString: string;
};

export default function Date({ dateString }: DateProps) {
  const date = parseISO(dateString);
  return <time dateTime={dateString}>{format(date, 'yyyy年MM月dd日', { locale: ja })}</time>;
}

これで、Markdownファイルからデータを読み込み、表示するための基本的な機能が揃いました。次の章では、Markdownをパースして実際にHTMLとして表示する方法を解説します。すべての記事を取得する関数

ブログの記事一覧ページなどで使用するために、すべての記事を取得する関数も実装します。

// lib/posts.ts(続き)
export function getAllPosts(): Post[] {
  const fileNames = fs.readdirSync(postsDirectory);
  const allPosts = fileNames
    .filter(fileName => fileName.endsWith('.md'))
    .map(fileName => {
      // ファイル名から.mdを削除してスラッグを取得
      const slug = fileName.replace(/\.md$/, '');
      
      // 記事の内容を取得
      const post = getPostBySlug(slug);
      
      return post;
    });

  // 日付でソート(新しい記事が先)
  return allPosts.sort((a, b) => {
    if (a.date < b.date) {
      return 1;
    } else {
      return -1;
    }
  });
}

タグごとの記事を取得する関数

タグページを実装するために、特定のタグに関連する記事を取得する関数も用意します。

// lib/posts.ts(続き)
export function getPostsByTag(tag: string): Post[] {
  const posts = getAllPosts();
  return posts.filter(post => post.tags.includes(tag));
}

すべてのタグを取得する関数

タグクラウドやタグメニューを作成するために、ブログ内のすべてのタグを取得する関数も実装します。

// lib/posts.ts(続き)
export function getAllTags(): string[] {
  const posts = getAllPosts();
  const tagsSet = new Set<string>();
  
  posts.forEach(post => {
    post.tags.forEach(tag => {
      tagsSet.add(tag);
    });
  });
  
  return Array.from(tagsSet);
}

実装のポイント

この実装方法には以下のようなメリットがあります:

  1. Gitと統合: Markdownファイルはプロジェクトリポジトリに含まれるため、Git履歴で変更を追跡できます。
  2. エディタフレンドリー: 一般的なテキストエディタや専用のMarkdownエディタで記事を編集できます。
  3. 型安全: TypeScriptの型定義により、記事データの構造が明確になります。
  4. バックアップが容易: テキストファイルなので、バックアップが簡単です。
  5. パフォーマンス: 静的ファイルなので、データベースアクセスよりも高速です。

Next.jsとTypeScriptの組み合わせにより、コンテンツ管理とフロントエンド開発を統合したシンプルかつ効率的なブログシステムを構築できます。効率的なデータ取得の実装

ブログでよく使われる機能として、「全記事の取得」「特定の記事の取得」「タグによるフィルタリング」などを実装しましょう。

// lib/posts.ts (続き)

// 全記事のメタデータを取得
export function getAllPosts(): Post[] {
  const fileNames = fs.readdirSync(postsDirectory);
  const allPosts = fileNames
    .filter((fileName) => fileName.endsWith('.md'))
    .map((fileName) => {
      // ファイル名から.mdを取り除いてスラッグとして使用
      const slug = fileName.replace(/\.md$/, '');
      return getPostBySlug(slug);
    });

  // 日付順にソート(新しい記事が先頭に来るように)
  return allPosts.sort((a, b) => {
    if (a.date < b.date) {
      return 1;
    } else {
      return -1;
    }
  });
}

// 特定のタグを持つ記事を取得
export function getPostsByTag(tag: string): Post[] {
  const allPosts = getAllPosts();
  return allPosts.filter((post) => 
    post.tags.some((t) => t.toLowerCase() === tag.toLowerCase())
  );
}

// すべてのタグとその記事数を取得
export function getAllTags(): { [tag: string]: number } {
  const allPosts = getAllPosts();
  const tagCounts: { [tag: string]: number } = {};
  
  allPosts.forEach((post) => {
    post.tags.forEach((tag) => {
      if (tag in tagCounts) {
        tagCounts[tag]++;
      } else {
        tagCounts[tag] = 1;
      }
    });
  });
  
  return tagCounts;
}

ファイル構成の例

実際のブログでは、以下のようなディレクトリ構成でMarkdownファイルを管理するとよいでしょう。

posts/
├── getting-started-with-nextjs.md
├── typescript-tips-and-tricks.md
├── ssg-vs-ssr.md
└── optimizing-nextjs-performance.md

この構造は、記事が増えてもGitでの管理がしやすく、また検索やフィルタリングも実装しやすいというメリットがあります。

まとめ:あなただけのSSGブログを育てていくために

この記事では、Next.jsとTypeScriptを使ったSSGブログの作り方を初心者向けに解説しました。主なポイントをまとめると:

  1. 高性能な技術スタック: Next.jsのSSG機能とTypeScriptの型安全性により、高速で保守性の高いブログを構築できます。

  2. シンプルなコンテンツ管理: Markdownファイルでコンテンツを管理することで、データベースなしでもGitベースのワークフローが実現できます。

  3. SEO対策の容易さ: Next.jsのコンポーネント(HeadやImage)を使用することで、検索エンジン対策が簡単に実装できます。

  4. デプロイの簡便さ: VercelなどのプラットフォームにGitHubリポジトリを連携するだけで、継続的デプロイが設定できます。

このような技術スタックを選択することで、高速でSEOに強く、開発効率の良いブログサイトを構築することができます。特に技術ブログなどコードを多く含むコンテンツの場合、TypeScriptとNext.jsの組み合わせは非常に相性が良いと言えるでしょう。

これらの基礎を理解した上で、あなた自身のアイデアを取り入れたオリジナルのブログサイトを作成し、さらに機能を拡張していってください。継続的な学習と改善を重ねることで、より質の高いWebサイトを構築する力が身につきます。

最後に、Next.jsとTypeScriptの公式ドキュメントやコミュニティリソースを活用して、最新の情報をキャッチアップすることも忘れないでください。あなたのWeb開発の旅が実り多いものになることを願っています。

おすすめコンテンツ