YuTa Extend

YuTaの個人開発と趣味のブログ

【入門】Next.jsを使って0からブログを作成する

2022-05-21  

blog-001-nextjs-ssg-blog

はじめに

JavaScript ライブラリである React 開発におけるフレームワーク Next.jsSSG の機能を使ってブログを作りました。 本記事では実際に Next.js を使ってブログを作成し、それを GitHub 経由で Cloudflare Pages にデプロイして公開するまでの流れを紹介します。最後のカスタムドメインの登録の部分だけお金がかかりますが、それ以外の部分は PC とネット環境さえあれば無料です。

SSG とは

SSG とは、静的サイトジェネレーター(Static Site Generator)の略で、その名の通り 静的サイト を生成できるシステムです。WordPress などの一般的に普及している Web サイトは 動的サイト で、アクセスがあるたびにシステムがサーバーのデータベースから情報を取得して Web ページを組み上げて表示するものですが、SSG ではあらかじめ組み上げておいた Web ページをそのまま配信する形になります。そのため、Web ページ組み上げに必要なシステムやデータベースを置いておくためのハイコストなサーバーが不要で、かつ素早く安全に Web ページを表示できるというメリットがあります。特に 動的サイトのためにレンタルサーバーを借りる場合、月額で普通は 1,000 円以上かかる ので、そこから解放されるメリットは大きいと思います。

一方、SSG にはメリットだけでなくデメリットもいくつかあります。一つはプログラミングが現時点では必須という点です。二つ目は、事前に Web ページを組み上げる都合上、サイトの閲覧者から入力を受け付けて画面に反映したり、閲覧者ごとに見た目が異なるページを作成したりといったことができません。(ただし、Next.js は動的サイトをハイブリッドで作りこむこともできます) 上記を踏まえると、SSG はブログサイトやコーポレートサイトのように、頻繁に内容が変わるようなことがない Web サイトを作るのに適しています。

SSG は Next.js 以外にも GatsbyHugo などがあります。ただ、 個人的には Next.js が一番融通が利いてしっくりくる感じがしました。 (例に挙げた二つは公式サイトのテーマが充実してはいるものの、テーマ以外のことをやろうとすると難易度が高くなりがちです。)

環境構築

まず、ブログ作成のための開発環境の構築から行います。OS によって詳細手順が異なりますが、ここでは Windows の場合を紹介します。

  1. Node.js をインストールする

    Next.js は Node.js というシステムで動作します。というわけで、まずはこの Node.js をインストールします。以下のサイトからダウンロードし、インストールしましょう。

    https://nodejs.org/ja/download/

  2. Visual Studio Code をインストールする

    続けて、Next.js のコーディングをするためのツールを導入します。いくつか手段はありますが、 Visual Studio Code(以下、VSCode) が特におすすめです。非常に高速に動作し、様々なプログラミング言語の開発がこれ一つでできる上、Next.js 開発との相性も抜群です。というわけで以下のサイトからダウンロードし、インストールしましょう。

    https://code.visualstudio.com

  3. Git をインストールする

    Git というシステムもインストールしておきましょう。これは作成したコードの変更履歴を管理したりバックアップをとっておいてくれるシステムで、後述する GitHub と組み合わせると、ネット上にソースコードを置いて他の人と共有したり、後述する Web サイト公開用のシステムにソースコードを転送したりすることができます。

    https://gitforwindows.org/

  4. VSCode の拡張機能をインストールし、初期設定する

    次に、インストールした VSCode に 拡張機能 をインストールします。今回は以下の 4 つをインストールします。VSCode のショートカットキー[ctrl + shift + X]で拡張機能を開き、以下の機能をそれぞれ検索してインストールボタンを押します。

    • Japanese Language Pack for Visual Studio Code
    • ES7 React/Redux/GraphQL/React-N
    • ESLint
    • Prettier - Code formatter

    上から順に、VSCode の日本語化、React 関連の単語をハイライトしたり予測したりしてくれる機能、自動でコードチェックしてくれる機能、コードの保存時にコードのインデントなどを整えてくれる機能になります。

    なお、上 3 つはインストールした時点で自動で設定されますが、一番下の機能は手動で設定が必要になります。VSCode の[ctrl + ,]で設定画面を開き、 Format On Save にチェックを入れ、 Default FormatterPrettier -Code formatter を選択します。これで設定完了です。

  5. 作業フォルダを作成する

    最後に Next.js のプロジェクトを置く作業場所を作ります。任意の場所にフォルダを作成しておきましょう。作成後、VSCode の[ctrl + k][ctrl + o]でそのフォルダを開きます。

Next.js のプロジェクトを新規作成する

VSCode の[ctrl + @]で ターミナル を開き、以下のコマンドを入力します。最初のコマンドを実行するとプロジェクト名を聞かれるので、任意のプロジェクト名を入力しましょう。 以降は作成したプロジェクトのフォルダが作業フォルダになります。

Copied!!
$ npx create-next-app --typescript # 実際には、「$」ではなく「PS (フォルダパス)>」のようになっているかと思います。
$ cd (作成したプロジェクト名)    # 「cd」は作業フォルダ(カレントディレクトリといいます)を指定するコマンドです。

--typescriptオプションについてですが、これをつけると TypeScript の形式でコードを作成してくれます。TypeScript とは、通常の JavaScript に加えてデータ型やクラスを書けるようにした言語です。以降は TypeScript の形式(.tsx)でコードを書きますが、もし JavaScript の形式(.js)で書きたい場合は、以下の TypeScript の Prayground で JavaScript に翻訳をかけてくれるので、これをご活用ください。

https://www.typescriptlang.org/play

Next.js の初期画面を表示してみる

次にターミナルで以下のコマンドを実行し、Next.js の初期画面を表示してみます。

Copied!!
$ npm run dev

コマンドを実行すると、URL が表示されます(通常はlocalhost:3000)。その URL をクリックして Web ブラウザで以下のような画面が表示されれば成功です。(この時点ではまだネットには公開されません)

default-nextjs

なお、このコマンドでブラウザに画面を表示している間はコードを編集すると自動で画面に反映されますが、ターミナルが動きません。このモードは、[ctrl + c]で停止できます。

Next.js の余計なファイルを削除して最小限の構成にする

初期画面が無事表示されましたので、ここからはブログ構築に移ります。まずは最初に作成されたファイル群から不要なものを削除していきます。

  • pages/api
  • pages/api/hello.ts
  • styles/Home.module.css
  • public/vercel.svg

次に、プロジェクトフォルダ直下にsrcフォルダを作成し、その中にpagesフォルダとstylesフォルダを移動させます。

また、src/pages/index.tsxを以下のように最小限の内容にしておきます。return();で HTML タグを含むコードを返すと、それがそのまま HTML の文書としてページに表示されます。

src/pages/index.tsx
Copied!!
import type { NextPage } from "next";

const Home: NextPage = () => {
  return (
    <>
      <h2>hello world!!</h2>
    </>
  );
};

export default Home;

次に、こちらは任意ですが、Google Fonts などで<head>タグ内に<link>タグを記述したいときは、以下の内容のsrc/pages/_document.tsxというファイルを作成して<Head>タグ内に記述します。

src/pages/_document.tsx
Copied!!
import NextDocument, { Head, Html, Main, NextScript } from "next/document";

interface Props {}
class Document extends NextDocument<Props> {
  render(): JSX.Element {
    return (
      <Html lang="ja">
        <Head>
          {/* Google Fonts の<link>タグなどが必要であればこの部分に書く */}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default Document;

ここまでで以下のようなフォルダ構成になっているかと思います。

Copied!!
(プロジェクトフォルダ)
 ├─ /.next
 ├─ /node_modules
  ├─ /public
 │  └─ favicon.ico
 ├─ /src
 │  ├─ /pages
 │  │  ├─ _app.tsx
 │  │  ├─ _document.tsx
 │  │  └─ index.tsx
 │  └─ /styles
 │     └─ globals.css
  ├─ .eslintrc.json
  ├─ .gitignore
  ├─ next-env.d.ts
  ├─ next.config.js
  ├─ package-lock.json
  ├─ package.json
  ├─ README.md
  └─ tsconfig.json

なお、Next.js の基本ルールは以下の通りです。

  • 画像などのアセットファイルはpublicフォルダに格納する。
  • ソースファイル格納用にsrcフォルダを作ってもよい。
  • <link>タグは_document.tsx内に記述する。
  • サイト全体に関する共通設定は_app.tsx内に記述する。

ここまでで一旦、先ほども使ったコマンドで Web サイトの内容を確認してみます。

Copied!!
$ npm run dev

今度は以下のような画面が表示されるかと思います。

hello-world

Markdown 形式のファイルを読み込んで一覧表示する

続いてブログのコアの部分となる 記事ファイルを読み込んで表示する 部分を作成します。 記事ファイルは Markdown という形式で書いていきます。 ヘッダではタイトルや投稿日やタグなど記事自体の情報を、Markdown 本文では記事の内容を書きます。 ブログに表示する画像をpublicフォルダ内に格納する必要がある関係上、以下のようなフォルダ構造にすると記事のフォルダ内に画像を格納できて管理しやすいと思います。

Copied!!
/public
 ├─ /posts
 │  ├─ /blog-1
 │  │  ├─ index.md
 │  │  └─ 画像ファイル
 │  ├─ /blog-2
 │  │  ├─ index.md
 │  │  └─ 画像ファイル
 │  ├─ /blog-3
 │  │  ├─ index.md
 │  │  └─ 画像ファイル
  ....
public/posts/xxxxx/index.md
Copied!!
---
title: "投稿サンプル"
date: "2022-01-01"
tags: ["タグ1", "タグ2"]
---

### 見出し 1

本文です。

- 箇条書き
- 箇条書き
  - 箇条書き(インデント)
  - 箇条書き(インデント)

### 見出し 2

本文です。

1. 箇条書き

   インデントできます。

2. 箇条書き
3. 箇条書き

記事ファイルを作成したところで、記事の読み取りシステムを組んでいきます。 まずはコマンドで以下のパッケージをインストールします。

Copied!!
$ npm i gray-matter
$ npm i react-markdown

次にsrcフォルダにlibフォルダを作成し、以下のようにapi.tsファイルを作成します。

src/lib/api.ts
Copied!!
import fs from "fs";
import { join } from "path";
import matter from "gray-matter";

// Markdownファイルが格納されているディレクトリを取得する
// ※process.cwd() はカレントディレクトリ
const postsDirectory = join(process.cwd(), "public/posts");

// posts配下にあるフォルダ名(slug)をすべて取得する
export const getPostSlugs = () => {
  const allDirents = fs.readdirSync(postsDirectory, { withFileTypes: true });
  return allDirents
    .filter((dirent) => dirent.isDirectory())
    .map(({ name }) => name);
};

export type Post = {
  slug: string;
  content: string;
  title: string;
  date: string;
  tags: string[];
};

/**
 * 与えられたslugから記事の内容を取得して返す
 * @param slug
 * @param fields 取得したい項目
 */
export const getPostBySlug = (slug: string, fields: string[] = []) => {
  // ファイルを読み込む
  const fullPath = join(postsDirectory, slug, "index.md");
  const files = fs.readFileSync(fullPath, "utf8");
  const { data, content } = matter(files);

  const post: Post = {
    slug: "",
    content: "",
    title: "",
    date: "",
    tags: [],
  };

  // 指定された値を取得する
  fields.forEach((field) => {
    if (field === "slug") {
      post[field] = slug;
    }
    if (field === "content") {
      post[field] = content;
    }
    if (field === "title" || field === "date" || field === "tags") {
      post[field] = data[field];
    }
  });

  return post;
};

/**
 * すべての記事から指定したfieldsの値を取得する
 * @param fields 取得したい項目
 */
export const getAllPosts = (fields: string[] = []) => {
  const slugs = getPostSlugs();
  const posts = slugs
    .map((slug) => getPostBySlug(slug, fields))
    .sort((a, b) => {
      // 日付の降順でソート
      const slugA = a.date;
      const slugB = b.date;
      if (slugA < slugB) {
        return 1;
      } else {
        slugB < slugA;
      }
      return slugA <= slugB ? 1 : -1;
    });

  return posts;
};

記事の読み取りシステムができたところで、以下のようにindex.tsxを書き換えて関数を呼び出しましょう。 getStaticPropsという関数内で呼び出す必要があります。

src/pages/index.tsx
Copied!!
import { NextPage, InferGetStaticPropsType } from "next";
import { getAllPosts } from "../lib/api";
import Link from "next/link";

type Props = InferGetStaticPropsType<typeof getStaticProps>;

export const getStaticProps = async () => {
  const posts = getAllPosts(["slug", "title", "date", "tags"]);

  return {
    props: { posts },
  };
};

const Home: NextPage<Props> = ({ posts }) => (
  <ul>
    {posts?.map((post) => (
      <div key={post.slug}>
        <li>
          <Link href={`/posts/${post.slug}`}>
            <a>
              <h2>{post.title}</h2>
            </a>
          </Link>
          <p>{post.date}</p>
          <ul>
            {post.tags?.map((tag) => (
              <li key={tag}>{tag}</li>
            ))}
          </ul>
        </li>
      </div>
    ))}
  </ul>
);

export default Home;

ここまでできたら、npm run devで内容を確認してみましょう。以下のようになるかと思います。 投稿サンプル と書かれた部分が個別ページへのリンクになっています。

blog-index

さて、この状態ではリンクを張れたものの、リンク先に何もなく 404 ページが表示されてしまいます。 そこでリンク先として個別の文書を表示するページを作りましょう。 pagesフォルダの中にpostsフォルダを作成し、その中に[slug].tsxファイルを作成します。 ファイル名を[]でくくることで、 特定多数 のページを表現することができます。(動的サイトなら不特定多数のページも作成できますが、静的サイトの場合は事前の特定が必要になります) ファイル内のgetStaticPaths関数で作成するページの数とパスを特定し、それらに対してgetStaticProps関数でページに使う引数を設定する流れになります。

src/pages/posts/[slug].tsx
Copied!!
import { NextPage, InferGetStaticPropsType } from "next";
import { getAllPosts, getPostBySlug } from "../../lib/api";
import ReactMarkdown from "react-markdown";

type Props = InferGetStaticPropsType<typeof getStaticProps>;

export const getStaticPaths = async () => {
  const posts = getAllPosts(["slug"]);

  return {
    paths: posts.map((post) => {
      return {
        params: {
          slug: post.slug,
        },
      };
    }),
    fallback: false,
  };
};

export const getStaticProps = async ({ params }: any) => {
  // 記事を読み込む
  const post = getPostBySlug(params.slug, [
    "slug",
    "title",
    "date",
    "tags",
    "content",
  ]);

  return {
    props: {
      post,
    },
  };
};

const Post: NextPage<Props> = ({ post }) => (
  <article>
    <h2>{post.title}</h2>
    <p>{post.date}</p>
    <ul>
      {post.tags?.map((tag) => (
        <li key={tag}>{tag}</li>
      ))}
    </ul>
    <section>
      <ReactMarkdown>{post.content}</ReactMarkdown>
    </section>
  </article>
);

export default Post;

ここまで作成してnpm run devコマンドで画面を確認すると、個別記事のページを表示できていることが確認できます。

blog-detail

GitHub でソースコードを管理する

ブログの基本的な部分ができたところで、GitHub を使ってソースコードをネットに上げましょう。GitHub とは先述した Git をネット上で使えるクラウドサービスで、 リモートリポジトリ という箱を作ってその中にソースコードの変更履歴をためていくことができます。GitHub が一番有名ですが、後述する Cloudflare Pages では類似のシステムである GitLab も対応しているので、そちらを使っても構いません。

まずは以下のサイトで GitHub のアカウントを作成しましょう。

https://github.com/

次に、先ほど説明したリモートリポジトリを作成します。このとき public を選択すれば、ネット上にソースコードが公開される公開リポジトリを、 private を選択すれば招待したユーザしか見れない非公開リポジトリを作成できます。 注意点として、GitHub の公開リポジトリにファイルを上げた時点で、そのファイルは全世界から見れる状態になっています。 パスワードや個人情報などを載せないように気をつけましょう。 有料クラウドサービスのアカウントが流出して何千ドルも被害が出るようなケースもあります。非公開リポジトリであってもそういった情報はむやみに載せないのが鉄則です。

リモートリポジトリが作成できたところで、VSCode で Git を動かして GitHub にソースコードを上げていきます。初回は以下のとおりコマンドが多いですが、二回目以降は以下のとおりコマンドが少なく済みます。

Copied!!
# 初回
$ git init
$ git config user.name (GitHubのアカウントのユーザ名)
$ git config user.email (GitHubのアカウントのメールアドレス)
$ git remote add origin https://~(リモートリポジトリのURL).git
$ git branch -M master
$ git add *
$ git commit -m "first commit"
$ git push origin master
Copied!!
# 次回以降コミット(保存)したい場合
$ git add *
$ git commit -m "(保存時のコメント)"
$ git push origin master

GitHub では多人数でソースコードを編集することもできます。その場合はいろいろ作法がありますが、今回は個人開発なので割愛します。

なお、今回作成したブログサンプルについては、以下の GitHub のリポジトリに全コードを格納してありますので、詰まったら参考にしてみてください。

https://github.com/YuTaExtend/blog-sample

Cloudflare Pages でインターネットにブログを公開する

インターネットにブログを公開するには、ホスティングサービスと呼ばれるクラウドサービスを利用します。

Next.js のプロジェクトを自動で動作させてくれるホスティングサービスは主に VercelNetlifyCloudflare Pagesの 3 つがあります。この中で Vercel は無料枠での商用利用が不可 です。残りの 2 サービスでは Cloudflare Pages の方が高速なので、新規で商用ブログを作るなら Cloudflare Pages がおすすめです。(ただし Vercel の方がさらに高速で Next.js との相性も良いので、非商用目的なら Vercel の方がおすすめです)

というわけで、まずは以下の Cloudflare Pages で無料アカウントを作成しましょう。

https://pages.cloudflare.com/

アカウント作成後、Pages のプロジェクトを作成すると、GitHub のアカウントを連携できるようになっています。 アカウントの連携後にリポジトリを選択し、 Framework presetNext.js を選択して保存すれば設定完了です。

デプロイがうまくいけば公開用の URL が表示されます。その URL にアクセス(PC でもスマホでも OK)すれば、公開された Web サイトを見ることができます。

カスタムドメインを登録する

このままでも一応 Web サイトがネット上に公開されているのですが、サイトの URL の最初の部分(ドメインといいます)が ~.pages.dev になっていると思います。この状態ではネット上の信頼度にも影響し、検索の上位も出てくるのが難しくなるだけでなく、人気になったらなったで独自のカスタムドメインに引っ越すのも大変になります。

そこで、お試しでなく人目につくブログを運用する目的であれば、最初から自分で選んだドメインを設定することをおすすめします。レンタルサーバー会社を介さずにドメインを取得するには、 お名前ドットコム などのサービスで取得するのが一般的です。

https://www.onamae.com/

ここでカスタムドメインの使用権を購入し、取得したドメインを Cloudflare Pages の Custom Domains の欄で設定すれば自動で導入してくれます。ひとまず無料プランで大丈夫です。

最後に

いかがでしたでしょうか? Next.js を使えば、低価格でハイパフォーマンスなブログを作成することができます。手間はかかりますが、その分いい勉強になりました。

もちろん 今の状態ではほとんどまっさら ですが、他のブログサイトを習ってコードを追記したり CSS を適用したりすることで他と遜色ないブログに仕上げることができます。

ぜひ挑戦してみましょう。Try your best!

関連記事