Web開発に初めて触れてから1年ほど経った頃、自分だけのブログを持ちたいと思うようになった。
そこで、およそ6か月これだけにかかりきりになって作ってみることにした。

フロントエンド側の機能については、下記のキム・ドヒョンさんのブログを参考にするだけで十分だと思う。
自分も mdx を使ってブログを作るだけなら、1週間もかからなかった。
実のところ、ブログを運営するだけならフロントエンドだけあっても大きな問題はなかった。
しばらくは mdx を書いてフロントエンド開発だけで十分だと思っていたが、どこからでもWebにアクセスして記事を書けるフルスタックブログへの夢は捨てきれなかった。
1. 機能
機能は大したことはない。
記事を投稿して、確認できる。

ログインすると、右上のボタンの形が変わる。
そしてそれをクリックすると記事をアップロードできる。
下の動画を見ると、記事を書いてアップロードする一連の流れが分かる。
画像変換の処理があり、アップロードされたすべての画像は、最も効率が良い avif 形式で管理している。
YouTube は、URL を貼り付けるだけで上のように埋め込むことができる。
表(テーブル)管理機能のアイデアは Tistory から得た。

表にフォーカスが on になると、上部に表を追加・削除するツールバーが表示される。
もちろん下書き保存も可能だ。
エディタ内で cmd+s もしくは ctrl+s を押すと保存される。
2. アーキテクチャ
アーキテクチャというと何かカッコよく聞こえるが、韓国語にするとただの構造だ。
最初にサーバーへ入ってきたリクエストは、Caddy がリバースプロキシとして振り分けてくれる。
フロントエンドだけなら mdx をそのまま変換して返すだけでよかったが、バックエンドを作ったので、これを Next.js と Prisma でつないだ。

みんな分かっていることだが、ユーザーから来たデータをそのままバックエンドに突っ込んだら即ハッキングされる。
必ず API で検証してから保存する。

一度、ブログにコメントがたくさん付いてほしいという思いから、誰でもコメントを書けるように API を開放しておいたことがある。
するとあるヤツがその API に対してクエリインジェクション攻撃を浴びせ続け、コメントが100件ほど付いてしまっていた。
svg 画像の中に fetch とコマンドを埋め込んでハッキングを試みたヤツもいた。
もしかしたら中国のどこかで、知らないうちに自分の分身が元気に歩き回っているかもしれない。
3. エディタ

一番手をかけて作ったのがエディタだ。
エディタは Tiptap にさまざまな機能を追加して実装した。
その結果、Tiptap 関連の Component だけで10個を超えてしまった。

画像挿入のケースは、下記のように実装した。
const editor = useEditor({
extensions: editorExtensions,
content,
onUpdate: ({ editor }) => {
onChange(editor.getHTML());
},
editorProps,
immediatelyRender: false,
});
const { uploadProgress, uploadImage, clearProgress } = useImageUpload({
onUploadComplete: (imageUrl, isAnimated) => {
if (editor) {
try {
editor
.chain()
.focus()
.insertContent([
{
type: "image",
attrs: {
src: imageUrl,
alt: "アップロードされた画像",
"data-animated": isAnimated || false,
},
},
{
type: "paragraph",
content: [],
},
])
.run();
} catch (error) {
console.error("画像挿入エラー:", error);
}
}
},
});パフォーマンス上の利点を得るために、HTML をそのまま保存・ロードするようにした。
Tiptap からバックエンドに保存するときには、いくつか処理を追加した。
コードブロック、Google マップ、見出し、画像、リンクプレビューなど、さまざまな処理を事前に行ったうえで、content と processedContent を別々に保存した。
4. バックエンドスキーマ
最近、多言語サービスを実装してみて、一度作ったバックエンドスキーマを変更するのは災難だということを思い知った。
あらためて、スキーマの重要性を強調していた先生の言葉を思い出した。
もしブログのバックエンドまで考えているなら、どのようなサービスを提供するのかをじっくり長く考えてみてほしい。
あれこれ追加していたら、あまりにも巨大になってしまったスキーマなので恥ずかしいが公開してみる。
model Post {
id String @id @default(cuid())
title String
slug Int @unique @default(autoincrement())
excerpt String
content String
processedContent String?
thumbnail String?
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime?
likes Int @default(0)
views Int @default(0)
comments Comment[]
likesRows PostLike[]
viewDedups PostViewDedup[]
notifications Notification[]
locales PostLocale[]
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId String
category String
@@map("posts")
}5. 感想
ここで次の質問に答えてみようと思う。
ブログを作るのにバックエンドまで必要なのか?
全部作り終えてみると、わざわざ?という気もしてくる。
結局 WordPress がものすごく楽な道だった、ということに気づかされた。
バックエンドと API を実装した瞬間から、本当に考えなければならないことが一気に増える。
最近の react2shell 問題のときには、数分おきに攻撃を受け、ターミナルに見たこともないコマンドがどんどん出てきて、数日間つらい思いをした。
今でも数日に一度は npm audit でセキュリティチェックをして修正している。
たまに DB スキーマを変更してマイグレーションがこじれると、冷や汗が出る。
それでも振り返ってみると、バックエンドを実装しながら多くのことを学べたし、自分の手で機能を実装できるブログを持てたのは良かったと思う。
これからはブログの機能追加よりも、他のプロジェクトを進めて自分のポートフォリオをもっと充実させていきたい。








댓글을 불러오는 중...