どうも、@ryusei__46です。
この記事では、WordPressのように記事投稿が行えるCMSもどきをNext.jsで作ったので、これからNext.jsでブログやホームぺエージを運営したいという方は、是非テンプレートとして使ってみてください。
今回制作したブログテンプレートは、当サイトのソースそのままとなっているので、デザインを大幅に変更したい方は逆に手間になると思いますので、ゼロからNext.jsでコーディングした方が良いかもしれません。
最初から、Next.jsでブログやホームページのシステムをコツコツ開発していくのは、かなり骨が折れますからね。何分私は、学習しながら開発を進めていたので、形になるまでに2ヵ月半ほど要しました。
ページごとに都度ビルドして、完全なSSG(静的サイトジェネレータ)としてブログやホームページを作るのであれば、割とスムーズに開発が終わると思いますが、管理機能を付けようとすると一気に工数が跳ね上がりますね。
ただ、記事の管理機能を付けることで、
- 気軽に記事投稿ができる
- マークダウンやHTMLファイルをページごとに追加してビルドする必要がないので、工数が格段に抑えられる
- ビルドサイズが抑えられる
- ビルド時間が抑えられる
- 記事をデータベースで管理することによって関連記事表示やコメント機能、カテゴリーへの関連付けといったロジックが実装できる
など多くのCMSに欠かせない機能が実現できます。
ということで、これから私が制作した、テンプレートの開設をしていきます。
制作したブログテンプレートの仕様(暫定版)
※このブログテンプレートはまだ制作中であり、これから機能追加を順次行っていきます。また、まだバグも多々存在するので、使用する場合は都度ご自身で修正してもらう必要があります。
- 記事投稿と編集(下書き・パーマリンク設定を含む)
- メディアライブラリー
- カテゴリー管理
そのほかロジック・ページレイアアウトなどは、当サイトと同様なので、そのまま使えますが、個々にカスタマイズしていくことになると思います。
制作したブログテンプレートの構造・ファイル軍
本ブログテンプレートは、Next.js 14.0.2を使用し、App Routerで構築しています。
まずは、使用しているパッケージ群と開発環境を記載しておきます。
開発環境
OSには、Windows 11 Proを使用し、WSL2(Ubuntu 22.04)上で開発しています。Node.js 20.5.1、pnpm 8.10.2(パッケージマネージャー・npmより高速)を使用。
使用パッケージ群
dependencies:
@next-auth/prisma-adapter 1.0.7 file-type 18.5.0 next-auth 4.24.3 @prisma/client 5.4.2 jschardet 3.0.0 nodemailer 6.9.6
bcrypt 5.1.1 jsdom 22.1.0 sanitize-html 2.11.0 ckeditor5-custom-build file:lib/ckeditor5-custom negotiator 0.6.3 sharp 0.32.6
cookies-next 4.0.0 next 14.0.2
devDependencies:
@formatjs/intl-localematcher 0.4.2 @types/negotiator 0.6.2
prisma 5.4.2 @fortawesome/fontawesome-svg-core 6.4.2
@types/node 20.8.7 react 18.2.0
@fortawesome/free-brands-svg-icons 6.4.2 @types/nodemailer 6.4.13
react-dom 18.2.0 @fortawesome/free-regular-svg-icons 6.4.2
@types/react 18.2.31 react-hook-form 7.47.0
@fortawesome/free-solid-svg-icons 6.4.2 @types/react-dom 18.2.14
react-select 5.7.7 @fortawesome/react-fontawesome 0.2.0
@types/sanitize-html 2.9.3 sass 1.69.4
@types/bcrypt 5.0.1 animate.css 4.1.1
tocbot 4.21.2 @types/jsdom 21.1.4
highlight.js 11.8.0 typescript 5.2.2
ckeditor5-custom-build
パッケージは、CkEditor 5をカスタムビルドしたものになります。公式が提供するプラグインと独自で実装したプラグインを同梱しています。
ディレクトリ構造
├── app
│ └── [lang] // 言語判定用パラメータ
│ ├── admin // 管理パネルのページ群
│ │ ├── category
│ │ ├── media
│ │ ├── post
│ │ └── post-list
│ ├── api
│ │ ├── auth // 管理パネルへのログイン処理に使う認証処理
│ │ ├── blogcard // ブログカードの情報取得
│ │ ├── category
│ │ │ ├── create
│ │ │ ├── get
│ │ │ │ ├── related-post
│ │ │ │ └── route.ts
│ │ │ ├── slug-exist // スラグからカテゴリーを取得
│ │ │ └── update
│ │ ├── contact // お問い合わせで使うメール送信処理
│ │ ├── discussion // 記事のコメント管理
│ │ │ ├── create
│ │ │ └── get
│ │ ├── get-media
│ │ ├── get-media-first
│ │ ├── media-stream // 保存されたメディアから画像をストリームで配信する
│ │ │ ├── icon
│ │ ├── post // 記事情報管理
│ │ │ ├── create
│ │ │ ├── get
│ │ │ ├── get-many
│ │ │ └── update
│ │ ├── register // ユーザー登録
│ │ ├── rm-media // メディアの削除
│ │ ├── upload // メディアのアップロード
│ │ └── userexist // ユーザー存在チェック
│ ├── article // 記事ページ
│ │ ├── [permalink] // パーマリンクで記事を表示
│ ├── auth // ユーザー認証ページ
│ │ ├── login
│ ├── category // カテゴリーごとの新着記事表示
│ │ ├── [slug] // スラグを使って表示
│ └── privacy-policy // プライバシー・ポリシーページ
├── components
│ ├── small-parts
│ └── use-client
├── external // scriptタグで処理する必要のあるスクリプト
├── lib
│ └── ckeditor5-custom
├── locales // 多言語化に対応するための言語辞書ファイルとスクリプト
├── prisma // データベーススキーマの管理
│ └── migrations
└── types // TypeScript用型定義ファイル軍
├── define-type.d.ts
└── override-react.d.ts
上記のディレクトリ構造を見てもらえれば、おおむね全体像が理解できると思います。
制作したブログテンプレートの使い方
以下で簡単な使い方とカスタマイズの方法を説明していきます。
テンプレートのダウンロードと環境構築
以下で私のGitHubリポジトリからソースをダウンロードするか、git cloneでプロジェクトディレクトリにクローンしてください。
用意ができたら、プロジェクトのルートディレクトリで、pnpm install
コマンドを実行して必要な依存パッケージをインストールします。
.envファイルで環境変数を設定する
プロジェクトルートに「.env.sample」ファイルがあるので、まず「.sample」拡張子を削除して、「.env」ファイルに名前を変更してください。
作成した「.env」ファイルを開くと下記のような内容になっていると思うので、個々の環境に応じた値に全て変更してください。
# App settings
APP_ROOT_PATH=/var/www/next-creator // プロジェクトルートまでの絶対パス
API_ACCESS_ADDRESS="http://192.168.1.45:8648" // サーバーコンポーネントでfetchするとき用
NEXT_PUBLIC_SITE_TITLE="site title"
APP_URL=http://ryuseiweb.localhost
NEXT_PUBLIC_APP_URL=http://ryuseiweb.localhost
API_ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Google Analytics
NEXT_PUBLIC_GA_MEASUREMENT_ID = "G-xxxxxxxxxx"
# next-auth
NEXTAUTH_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
NEXTAUTH_URL=http://ryuseiweb.localhost
# smtp server setting
SMTP_HOST="smtp-mail.outlook.com"
SMTP_USER="xxxxxxx@xxxxxxxxxx"
SMTP_PASSWORD="xxxxxxxxxxx"
USE_ADDRESS="xxxxxxxxxxx@xxxxxxxxxxx"
# Prisma
DATABASE_URL="mysql://[username]:[password]@[hostname]:[port]/[databasename]"
API_ACCESS_ADDRESS
についてですが、SSRする時のサーバーコンポーネントでfetchする時に、何故かドメイン名にするとエラーが発生したため、IPアドレス直書きで対応しました。こちらはそれ以降検証していないので、Next.jsのバージョンが上がってバグが解消されたかもしれません。
メールの送信サーバーは、MicrosoftのOutlookを使ってますが、他のSMTPサーバーを使いたい場合には、適宜変更してください。しかし、サービスによってSMTPサーバーの仕様が異なるので、nodemailer
の設定を自身で変更してください。/api/[lang]/api/contact
にファイルがあります。
public.config.jsonファイルの設定
プロジェクトルートに、「public.config.json」ファイルがあります。このファイルでは、現時点でサイトの多言語化の設定を行っています。
{
"locale": {
"default": "ja",
"accept-lang": [ "ja", "en" ],
"labels": {
"ja": { "ja": "日本語", "en": "英語" },
"en": { "ja": "Japanese", "en": "English" }
}
}
}
"default"
キーには、デフォルトでサイトに適用される言語を設定します。"accept-lang"
キーには、サイト全体で使用可能な言語コードを配列で列挙します。"labels"
キーには、言語選択ドロップダウンに表示するラベルの設定を言語ごとに設定します。
※最低でも1つの言語コードを設定しなければなりません。
また、TypeScriptで使用する「型:AcceptLocales
」にも使用可能な言語コードを配列で設定しておきます。/types/define-type.d.ts
の3行目にあります。
Prismaでデータベースを初期化する
まず、以下のコマンドでprismaモデルからTypeScriptの型定義を生成します。
pnpx prisma generate
そして、以下のコマンドでデータベースを初期化します。
pnpx prisma migrate dev
これでデータベースに必要なテーブルが作成されたと思います。
サーバーの起動
pnpm run dev
コマンドでサーバーを起動します。http://localhost:8648
にアクセスするとブログのトップページが表示されます。
次に、http://localhost:8648/auth/login
にアクセスすると、ユーザー登録フォームが表示されるので、そのまま登録を行います。登録が成功すると自動でログインされます。
ログインした状態で、ページ上部に管理バーが表示されるので、そこから記事の編集やカテゴリー管理、メディアのアップロードを行います。
※現時点ではまだ最低限の機能しか用意できていないので、随時アップデートしていく予定です。
記事一覧画面の説明
このページでは、投稿した記事を一覧表示します。それぞれの投稿に対して、編集と削除が行えるようになっています。
記事投稿画面の説明
このページでは、記事の投稿と編集を行います。エディターは、「CkEditor 5」を使用しており、WordPressに搭載されているエディターと同じような感覚で使えます。少々手間ではありますが、独自のブロックやうぇじぇっとを扱えるようにしたい場合は、自分でプラグインを書く必要があります。私の方で、捕捉ブロックやブログカードブロック、メディア選択機能などはプラグインを作って組み込んであります。また、CkEditor開発元が提供している無料の公式プラグインで必要と思われるものについては、片っ端から追加してビルドしてあります。
エディター上部の言語名の書かれたタブでは、国の言語ごとに記事の内容を別で編集できるようになっています。このタブは、「public.config.json」で設定した言語設定に基づいて表示されます。
右サイドバーにある「サムネイル設定」では、記事のサムネイル画像が設定されます。サムネイル画像を設定していない場合は「No Image」画像が挿入されます。
「パーマリンク」では、記事を表示する際のURLに英数字で分かりやすい文字列を指定できます。WordPressのパーマリンクと同様です。特に指定しない倍には、記事のURLにidが挿入されます。パーマリンクを指定した場合:https://example.com/article/my_post_title
、指定しなかった場合:https://example.com/article?id=xx
という形式になります。
「カテゴリー設定」では、項目をクリックすることで設定したカテゴリー記事に関連付けできます。
最後に記事を投稿する場合は「公開」、下書きとして保存する場合は「下書きとして保存」をクリックして終了です。
メディアライブラリーの説明
メディアライブラリーでは、現状画像のアップロードのみが行えるようになっています。音声と動画ファイルのアップロードにも随時対応予定です。
動画のアップロードをするよりかは、YouTubeなどの動画投稿サイトに投稿し、プレイヤーを埋め込む形の方が、サーバーの容量を圧迫しないので良いと思います。
各メディアはクリックすると、拡大表示されます。
カテゴリー設定画面の説明
このページでは、カテゴリーの追加・編集・削除ができます。カテゴリーの設定項目では、カテゴリー名(言語ごとに表示するラベルを設定できる)、スラグ、ランク(もともとは表示の順序を設定する目的で用意しましたが、現状この値は使用していません)、親カテゴリーの選択、アイコン画像(カテゴリーラベルの左に表示するアイコン。設定しない場合はデフォルトのFont Awesomeアイコンが表示される。)が設定可能です。
捕捉事項
フロントページ部分は、当サイトと同じなので説明は省略します。レイアウトを個々にカスタマイズしたい場合には、ご自身で行ってください。
また、1つ留意点として、外部のスクリプトやスタイルシートの読み込みをしたい場合は、Content-Security-Policy
の設定を追加してください。/middleware.ts
にあります。
現在は以下のように設定しています。
// ・・・・・・・
const cspHeader = `
default-src 'self';
script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
style-src-elem 'self' https://fonts.googleapis.com 'unsafe-inline';
img-src 'self' https://* blob: data:;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
form-action 'self' *;
frame-ancestors 'none';
block-all-mixed-content;
upgrade-insecure-requests;
`;
// :::::::
おわりに
今回Next.jsでCMSもどきを製作したことで、WordPressを脱却できました。現状このシステムにとても満足しています。まだ、機能追加やバグ修正をしていく必要がありますが、学習しながら楽しくやっていきたいと思います。
今回開発したテンプレートには、コメントなどは一切付けていないので分かりにくい場所があるかもしれませんが、余裕がなかったのでご了承ください。何か分からないことや問題などあれば、この記事にコメントしていただくか、直接「お問い合わせ」よりご連絡ください。また、X(@ryusei_46)へのダイレクトメッセージでも構いません。