はじめに
筆者の作成したWebサービスのHacker Sheetを使ってNext.jsでブログサイトを構築する手順を紹介しています。興味のある方のみ読み進めてください。
Important
この記事では以下の環境での手順を紹介しています。他の環境の場合はコマンドやファイルパスなどを読み替える必要があります。
- macOS
- pnpm
- VS Code
基本的に「記載されているコマンドをそのまま実行していくことでブログサイトの構築が完了する」という内容の手順書です。新しく作成するファイルも少なく、内容をコピーして保存するだけになっているので初心者でも簡単に進めることができます。
今回はとりあえず動くものを作ってみることが主題なので細かい解説はありません。@hackersheet/*
のような独自のnpmパッケージの使い方などの詳細は別の記事を作成予定です。
Caution
この記事で紹介しているnpmパッケージ @hackersheet/*
はalpha版です。本番環境でのご利用にはご注意ください(2024/07/20時点)。
Node.js v20とpnpm環境のセットアップ
まずは、pnpmが使用できるか確認します。
pnpm -v # `9.5.0` のようにバージョンが表示されたら使用可能です
Node.js、pnpmがインストールされていない場合は下記の記事を参考にNode.jsとpnpmのセットアップをします。
Voltaでローカル環境にNode.jsをインストールしよう(macOS)
Next.jsアプリケーションの作成
Next.jsアプリケーションのプロジェクトは、下記のコマンドを実行することで簡単に作成できます。ターミナルを開いて実行します。
pnpm create next-app my-blog --ts --tailwind --eslint --app --src-dir --import-alias '@/*'
実行後にnpmパッケージのインストールが開始します。しばらく待ちます。
各オプションの簡単な説明は下記の通りです。より詳しく知りたい方はNext.jsの公式ドキュメントをご覧ください。
オプション | 説明 |
---|---|
my-blog | プロジェクト名です。同じ名前のディレクトリが作成されます |
--ts | TypeScripを使用します |
--tailwind | Tailwind CSSを使用します |
--eslint | ESLintを使用します |
--app | App Routerを使用します |
--src-dir | src/ ディレクトリを作成します |
--import-alias '@/*' | Import aliasを @/* に設定します |
Success! Created my-blog at /path/to/my-blog
というメッセージが表示されたら作成完了です。次の手順に進みます。
プロジェクトをVS Codeで開く
code my-blog
コマンドを実行してVS Codeを開きます。
Tip
VS Codeを開いて control
+ ~
を押すとVS Codeのターミナルを開くことができます。以降のコマンドはVS Codeのターミナルで実行することをお勧めします。
npmパッケージのアップデート
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」などの適当なメッセージでコミットしておくと変更点がわかりやすくなるのでオススメです。
ローカルサーバーの起動
この時点で間違いがないか確認するためにローカルサーバーでプロジェクトが起動できるかどうか確認してみましょう。
pnpm dev
上記のコマンドを実行後にhttp://localhost:3000 をブラウザで開いて、下記の画像のような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パッケージを次のコマンドでまとめて追加します。
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 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のセットアップを行います。
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 *
変更を確認してコミットします。
環境変数の設定
touch .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の変更
/** @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
を変更します。
以下の /* ここから下の部分を追加 */
より下にある内容を追加してください。
@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を作成
mkdir -p src/lib/hackersheet/ && touch 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
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サンプルページのコードを置き換えるので差分が多いです。
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 作成
mkdir -p 'src/app/posts/[documentSlug]/' && touch '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>
);
}
最後にローカルサーバーの起動
pnpm dev
コマンドを実行して http://localhost:3000 を確認します。
このような画面が表示されます。リストのタイトルをクリックすると個別ページに移動します。個別ページが表示されたら成功です。
以上ですべての手順完了です。