はじめに
エンジニアとして活動する中で、「自分のポートフォリオサイト」はただの実績紹介ページ以上の価値を持つべきだと常々考えていました。 それは 「最新技術の実験場」 であり、「日々の学習のアウトプット基盤」 であるべきだと。
そこで今回、単なる静的サイトだったポートフォリオをフルリニューアルし、「管理者用CMS」 と 「メンバー限定エリア」 を備えたWebアプリケーションへと進化させました。 既存のCMSサービス(ContentfulやMicroCMSなど)を使わず、認証から記事投稿まで全て自前で実装した技術的な裏側を共有します。
🏗️ 全体アーキテクチャ
今回のリニューアルにおける技術選定です。「個人開発としての身軽さ」 と 「拡張性」 のバランスを重視しました。
🔐 1. 異なる権限を共存させる「ハイブリッド認証」
今回の最大の挑戦は、ひとつのアプリ内に全く異なる2つの認証フローを持たせることでした。
Admin (私): Googleアカウントでセキュアにログインし、記事投稿や管理を行う。
Member (友人・クライアント): 共通の「合言葉(パスワード)」だけで簡易ログインし、限定コンテンツを見る。
Genetics User (研究用): 特定の教育システムにアクセスするための専用入り口。
実装のアプローチ
NextAuth.jsの Author 機能を拡張し、ログイン方法によって自動的に Access Role を振り分けるロジックを組みました。
export const authOptions: NextAuthOptions = {
providers: [
// 1. Google Provider (管理者用)
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
// 2. Credentials Provider (メンバー用)
CredentialsProvider({
name: "Member Login",
credentials: { password: { label: "Key", type: "password" } },
async authorize(credentials) {
// 環境変数のキーと一致すれば Member 権限を付与
if (credentials?.password === process.env.MEMBER_PASSWORD) {
return { id: "member", name: "Guest User", role: "member" };
}
return null;
}
})
],
callbacks: {
// JWT生成時に Role を埋め込む
async jwt({ token, user }) {
if (user) {
// Googleログインの場合、メールアドレスでAdmin判定
if (user.email === process.env.ADMIN_EMAIL) {
token.role = "admin";
} else {
token.role = user.role || "user";
}
}
return token;
},
// セッション呼び出し時にクライアントへ Role を渡す
async session({ session, token }) {
session.user.role = token.role;
return session;
}
}
};
これにより、フロントエンド側では useSession() を呼ぶだけで「このユーザーはAdminか?Memberか?」を即座に判断できるようになりました。
🛡️ 2. Middlewareによる鉄壁のルート保護
ページコンポーネントごとに if (!session) return を書くのは、保守性が低くセキュリティホールの原因になります。 今回は
middleware.ts
を使い、URL階層ごとのアクセス制御を一元管理しました。
/admin/**: Admin 権限が必須。Memberがアクセスすると403エラー。
/members/**: Admin または Member 権限が必要。未ログインならログイン画面へ。
/members/chat: 特定の許可されたユーザーのみアクセス可能(RBAC)。
// パスベースで権限をチェック
if (pathname.startsWith("/admin") && token?.role !== "admin") {
return NextResponse.redirect(new URL("/login?error=AccessDenied", req.url));
}
この設計により、将来ページが増えても自動的にセキュリティがかかる安心感があります。
📝 3. ブラウザで完結する「自作CMS」
従来の更新フローは「VSCodeを開く → Markdownを書く → Commit → Push → Vercelデプロイ」という手順が必要で、スマホや移動中には不可能でした。 そこで、「管理画面にGUIエディタを作り、API経由でサーバー上のMDXファイルを更新する」 仕組みを構築しました。
投稿フロー
1.記事作成画面 (/admin/posts/new) にアクセス。
2.タイトル、タグ、本文を入力して「Publish」ボタンを押下。
3.API Route (POST /api/admin/posts) がリクエストを受け取る。
4.サーバーサイドでMDXファイルを生成し、src/content/blog/ ディレクトリに保存。
5.(ローカル環境では)ファイル変更を検知してホットリロード、即座にブログ一覧に反映。
「SNS連携機能」
記事を書いた後、一番面倒なのが「SNSへの告知」です。 これを自動化するため、投稿完了画面に 「Copy for SNS」 ボタンを実装しました。
ボタンを押すと、以下のフォーマットでクリップボードにコピーされます:
📝 New Blog Update: [記事タイトル]
[記事の要約/Excerpt...]
Read more: https://taichi-portfolio.com/blog/[slug]
#Nextjs #Engineering #Portfolio
あとはX (Twitter) や Threads に貼り付けるだけ。 「書く」から「届ける」までの動線 を極限まで短縮しました。
🎨 4. 「使いたくなる」UIデザイン (Glassmorphism & Motion)
自分自身が毎日触るツールだからこそ、UIには徹底的にこだわりました。
Glassmorphism: 画面全体に backdrop-filter: blur(20px) を効かせた「すりガラス」のようなカードデザインを採用。背景のグラデーションが微かに透けることで、奥行きと高級感を演出しています。
Motion: ログイン画面のタブ切り替えには Framer Motion の layoutId を使用。選択中のタブ背景がスルスルと移動するアニメーションは、触っていて非常に気持ちが良いです。
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-accent rounded-md"
/>
たった数行のコードですが、これがあるだけでアプリの「質感」が劇的に向上します。
🚀 5. 今後の展望:教育×テックの実験場へ
今回のリニューアルで手に入れたのは、単なるWebサイトではなく 「ユーザー認証を持ち、コンテンツを動的に管理できるプラットフォーム」 です。
今後はこの基盤・Memberエリアを活用し、以下のような実験的なコンテンツを展開予定です。
遺伝子解析教育システム: 認証済みリサーチャー向けの専用ダッシュボード。
クローズドな技術共有チャット: 特定のメンバーとリアルタイムに技術議論を行うチャットルーム。
投資/ヘルスケアレポート: 一般公開できない詳細な分析データの提供。
個人開発の強みは「思いついたらすぐ実装できる」こと。 この最強の基盤を使って、今後もバリバリ開発を楽しんでいこうと思います!
