🍹

初心者でも簡単にNext.jsでブログサイトが作れる手順書

公開日
約2か月前
2024-07-20
更新履歴

はじめに

筆者の作成したWebサービスのHacker Sheetを使ってNext.jsでブログサイトを構築する手順を紹介しています。興味のある方のみ読み進めてください。

Important

この記事では以下の環境での手順を紹介しています。他の環境の場合はコマンドやファイルパスなどを読み替える必要があります。

  • macOS
  • pnpm
  • VS Code

基本的に「記載されているコマンドをそのまま実行していくことでブログサイトの構築が完了する」という内容の手順書です。新しく作成するファイルも少なく、内容をコピーして保存するだけになっているので初心者でも簡単に進めることができます。

今回はとりあえず動くものを作ってみることが主題なので細かい解説はありません。@hackersheet/* のような独自のnpmパッケージの使い方などの詳細は別の記事を作成予定です。

Caution

この記事で紹介しているnpmパッケージ @hackersheet/* はalpha版です。本番環境でのご利用にはご注意ください(2024/07/20時点)。

Node.js v20とpnpm環境のセットアップ

まずは、pnpmが使用できるか確認します。

Terminal
pnpm -v # `9.5.0` のようにバージョンが表示されたら使用可能です

Node.js、pnpmがインストールされていない場合は下記の記事を参考にNode.jsとpnpmのセットアップをします。

Voltaでローカル環境にNode.jsをインストールしよう(macOS)

Next.jsアプリケーションの作成

Next.jsアプリケーションのプロジェクトは、下記のコマンドを実行することで簡単に作成できます。ターミナルを開いて実行します。

Terminal
pnpm create next-app my-blog --ts --tailwind --eslint --app --src-dir --import-alias '@/*'

実行後にnpmパッケージのインストールが開始します。しばらく待ちます。

各オプションの簡単な説明は下記の通りです。より詳しく知りたい方はNext.jsの公式ドキュメントをご覧ください。

オプション説明
my-blogプロジェクト名です。同じ名前のディレクトリが作成されます
--tsTypeScripを使用します
--tailwindTailwind CSSを使用します
--eslintESLintを使用します
--appApp Routerを使用します
--src-dirsrc/ ディレクトリを作成します
--import-alias '@/*'Import aliasを @/* に設定します

Success! Created my-blog at /path/to/my-blog というメッセージが表示されたら作成完了です。次の手順に進みます。

プロジェクトをVS Codeで開く

Terminal
code my-blog

コマンドを実行してVS Codeを開きます。

Tip

VS Codeを開いて control + ~ を押すとVS Codeのターミナルを開くことができます。以降のコマンドはVS Codeのターミナルで実行することをお勧めします。

npmパッケージのアップデート

Terminal
pnpm update

まず、上記のコマンドでnpmパッケージをアップデートします。

WARN  5 deprecated subdependencies found: @humanwhocodes/config-array@0.11.14, @humanwhocodes/object-schema@2.0.3, glob@7.2.3, inflight@1.0.6, rimraf@3.0.2

のようなワーニングが出ますが気にせず進めます(2024/07/20時点)。

control + shift + G を押すとサイドバーに「ソース管理」が表示されます。次のファイルが変更されています。

  • package.json
  • pnpm-lock.yaml

手順毎に「wip」などの適当なメッセージでコミットしておくと変更点がわかりやすくなるのでオススメです。

ローカルサーバーの起動

この時点で間違いがないか確認するためにローカルサーバーでプロジェクトが起動できるかどうか確認してみましょう。

Terminal
pnpm dev

上記のコマンドを実行後にhttp://localhost:3000 をブラウザで開いて、下記の画像のようなNext.jsのサンプルページが表示されたら成功です。

Next.jsサンプルページ

確認できたらターミナルで control + Cを押してローカルサーバーを停止します。

npmパッケージを追加

  • @hackersheet/core
  • @hackersheet/next-document-content-components
  • @hackersheet/next-document-content-kifu
  • @hackersheet/react-document-content
  • @hackersheet/react-document-content-styles
  • katex

今回使用するこれらのnpmパッケージを次のコマンドでまとめて追加します。

Terminal
pnpm add @hackersheet/core@alpha @hackersheet/next-document-content-components@alpha @hackersheet/next-document-content-kifu@alpha @hackersheet/react-document-content@alpha @hackersheet/react-document-content-styles@alpha katex

実行後に新しく以下のWARNが表示されますが、動作に影響はないので無視して進めます(2024/07/20時点)。

実行後のWARN
WARN Issues with peer dependencies found
.
└─┬ @hackersheet/next-document-content-kifu 0.1.0-alpha.2
  └─┬ kifu-for-js 5.4.1
    ├── unmet peer react@^16.14.0: found 18.3.1
    ├── unmet peer react-dom@^16.14.0: found 18.3.1
    └─┬ mobx-react 6.3.1
      ├── unmet peer react@"^16.8.0 || 16.9.0-alpha.0": found 18.3.1
      └─┬ mobx-react-lite 2.2.2
        └── unmet peer react@^16.8.0: found 18.3.1

control + shift + G で「ソース管理」を開きます。次のファイルが変更されています。

  • package.json

    package.json
    // ...
    "dependencies": {
      "@hackersheet/core": "0.1.0-alpha.4", 
      "@hackersheet/next-document-content-components": "0.1.0-alpha.5", 
      "@hackersheet/next-document-content-kifu": "0.1.0-alpha.1", 
      "@hackersheet/react-document-content": "0.1.0-alpha.5", 
      "@hackersheet/react-document-content-styles": "0.1.0-alpha.4", 
      "katex": "^0.16.11", 
      "next": "14.2.5",
      "react": "^18.3.1",
      "react-dom": "^18.3.1"
    },
    // ...
  • pnpm-lock.yaml

変更を確認してコミットします。

shadcn/uiのセットアップ

コンポーネントライブラリのshadcn/uiのセットアップを行います。

Terminal
pnpm dlx shadcn-ui@latest init --yes

コマンドを実行すると対話形式のセットアップが開始します。次のように進めていきます。

  • Which style would you like to use?

    Default

  • Which color would you like to use as base color?

    Slate

  • Would you like to use CSS variables for colors?

    yes

セットアップが完了すると以下のファイルが作成・変更されます(末尾の*は新規作成されたファイルを示しています)。

  • components.json *
  • package.json
  • pnpm-lock.yaml
  • tailwind.config.ts
  • src/app/globals.css
  • src/lib/utils.ts *

変更を確認してコミットします。

環境変数の設定

Terminal
touch .env.development.local

.env.development.local を作成して以下の内容で保存します。

.env.development.local
HACKERSHEET_API_ENDPOINT=https://api.hackersheet.com/example/v1/graphql
HACKERSHEET_API_ACCESS_KEY=hsws_TVZ6MjdnMUNrWXdyRjZ5SEZSOFp3OWVXS0ZiR3lHSFE6akdzVzQ5WlRhc0RwRm1ZWGRpZWl5aHZpM2ZtSlhSOG42ZExEbWZMQXd1c2dwdXZ0

next.config.mjsの変更

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: { 
    formats: ["image/webp"], 
    remotePatterns: [ 
      { 
        protocol: "https", 
        hostname: "public-content.hackersheet.com", 
        pathname: "/**", 
      } 
    ], 
  }, 
};

export default nextConfig;

src/app/globals.cssの変更

src/app/globals.css を変更します。

以下の /* ここから下の部分を追加 */ より下にある内容を追加してください。

globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  /* ... */
  /* ... */
  /* ... */
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

/* ここから下の部分を追加 */
@layer base {
  :root {
    --hsdc-scroll-margin-top: 0px;
    --hsdc-font-code: monospace, sans-serif;
    --hsdc-border: var(--border);
    --hsdc-muted: var(--muted);
    --hsdc-muted-foreground: var(--muted-foreground);
    --hsdc-link: var(--primary);
    --hsdc-code-block: var(--muted);
    --hsdc-shiki-diff-add: 160 84% 39%;
    --hsdc-shiki-diff-remove: 350 89% 60%;
    --hsdc-shiki-highlighted-word: 60 100% 50%;
  }

  :root {
    --github-alert-default-color: rgb(208, 215, 222);
    --github-alert-note-color: rgb(9, 105, 218);
    --github-alert-tip-color: rgb(26, 127, 55);
    --github-alert-important-color: rgb(130, 80, 223);
    --github-alert-warning-color: rgb(191, 135, 0);
    --github-alert-caution-color: rgb(207, 34, 46);

    .dark {
      --github-alert-default-color: rgb(48, 54, 61);
      --github-alert-note-color: rgb(31, 111, 235);
      --github-alert-tip-color: rgb(35, 134, 54);
      --github-alert-important-color: rgb(137, 87, 229);
      --github-alert-warning-color: rgb(158, 106, 3);
      --github-alert-caution-color: rgb(248, 81, 73);
    }
  }
}

src/lib/hackersheet/client.tsを作成

Terminal
mkdir -p src/lib/hackersheet/ && touch src/lib/hackersheet/client.ts

上記のコマンドを実行して下記の内容で保存します。

src/lib/hackersheet/client.ts
import { createClient } from '@hackersheet/core'
import { cache } from 'react'

const client = cache(() =>
  createClient({
    url: process.env.HACKERSHEET_API_ENDPOINT!,
    accessKey: process.env.HACKERSHEET_API_ACCESS_KEY!,
  })
)()

export { client }

src/app ディレクトリのファイルを変更

  • src/app/layout.tsx
  • src/app/page.tsx

src/app ディレクトリの、これらの2つのファイルを変更します。以下の内容にそのまま置き換えます。

src/app/layout.tsx

src/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Link from "next/link"; 

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <header className="mx-auto max-w-screen-sm">
          <div className="text-lg py-10"><Link href="/">my blog</Link></div>
        </header>

        {children}
      </body>
    </html>
  );
}

src/app/page.tsx

Next.jpサンプルページのコードを置き換えるので差分が多いです。

src/app/page.tsx
import { client } from "@/lib/hackersheet/client";
import Link from "next/link";

export default async function Home() {
  const { documents } = await client.getDocuments();

  return (
    <main className="mx-auto max-w-screen-sm">
      <ul className="list-disc">
        {documents &&
          documents.map((document) => (
            <li key={document.id} className="my-2">
              <Link href={`/posts/${document.slug}`}>{document.title}</Link>
            </li>
          ))}
      </ul>
    </main>
  );
}

src/app/posts/[documentSlug]/page.tsx 作成

Terminal
mkdir -p 'src/app/posts/[documentSlug]/' && touch 'src/app/posts/[documentSlug]/page.tsx'

src/app/posts/[documentSlug]/page.tsx を作成し、以下の内容で保存します。

src/app/posts/[documentSlug]/page.tsx
import { client } from "@/lib/hackersheet/client";
import {
  CodeBlock,
  Image,
  Link,
  LinkCard,
  Mermaid,
  XPost,
  Youtube,
} from "@hackersheet/next-document-content-components";
import { Kifu, KifuTo } from "@hackersheet/next-document-content-kifu";
import { notFound } from "next/navigation";
import { DocumentContent } from "@hackersheet/react-document-content";
import documentContentStyle from "@hackersheet/react-document-content-styles/basic";

import "katex/dist/katex.min.css";

export default async function PostPage({
  params: { documentSlug },
}: {
  params: { documentSlug: string };
}) {
  const { document } = await client.getDocument({ slug: documentSlug });

  if (!document) notFound();

  return (
    <main className="mx-auto max-w-screen-sm">
      <h1 className="text-xl pt-10 pb-20">{document.title}</h1>

      <DocumentContent
        document={document}
        style={documentContentStyle}
        permaLinkFormat="/posts/{{slug}}"
        components={{
          codeBlock: CodeBlock,
          image: Image,
          kifu: Kifu,
          kifuTo: KifuTo,
          link: Link,
          linkCard: LinkCard,
          mermaid: Mermaid,
          xPost: XPost,
          youtube: Youtube,
        }}
      />
    </main>
  );
}

最後にローカルサーバーの起動

Terminal
pnpm dev

コマンドを実行して http://localhost:3000 を確認します。

my blog

このような画面が表示されます。リストのタイトルをクリックすると個別ページに移動します。個別ページが表示されたら成功です。

以上ですべての手順完了です。