2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ポートフォリオサイト作ってみた

2
Last updated at Posted at 2026-05-29

はじめに

就活に向けて今まで作ってきた作品を纏めるためのポートフォリオを作成しました。
最近バイブで作ってばかりなので勉強用としてコードは基本写経か自分で書くようにしました。

Vercilのデプロイの際に沼ったのでその個所だけCloud codeにやってもらいました。原因は似た名前の別リポジトリの参照だったので完全に私が悪いです。はい。

フロントを自分で書く機会があまりないのでTailwind CSSのようなCSSフレームワークを勉強するいい機会になったと思います。
今回はNotion APIとfiguma Makeを使ってお手軽にポートフォリオサイトを作ってみたのでその解説をします。

技術構成

  • React
  • TypeScript
  • Vite
  • Tailwind CSS
  • Express
  • Notion API
  • Vercel

ディレクトリ構成

src配下にReact側の画面実装、server配下にExpress側のAPI実装を置くことで、フロントエンドとバックエンドを同じリポジトリで管理しています。

my-app/src/
├── pages/ # ページ単位のコンポーネント
│ ├── Home.tsx # トップページ
│ ├── AllProjects.tsx # プロジェクト一覧
│ ├── Career.tsx # 経歴
│ ├── Events.tsx # イベント参加履歴
│ └── TechStack.tsx # 技術スタック
├── components/ # 再利用可能なコンポーネント
│ ├── Header.tsx
│ ├── Hero.tsx
│ ├── Projects.tsx # Notionからデータ取得
│ ├── Stats.tsx # Notionからデータ取得
│ ├── TechStackPreview.tsx
│ ├── Footer.tsx
│ └── ui/ # 汎用UIコンポーネント
│ ├── badge.tsx
│ ├── button.tsx
│ └── card.tsx
└── App.tsx # ルーティング定義

画面設計

フロントの画面作成はFigma Makeで作成。
figma Makeではコンポーント単位で分解して作るように指示
例としてトップページ(Home.tsx)では以下のコンポーネントを組み合わせています

  • ヘッダー
  • プロフィール部分
  • 技術スタック表示
  • 制作物カード
  • 制作物一覧
  • フッター

figma Makeでフロントを作ってもらう手順

1.figmaにログインして右上のMakeを選択します
image.png

2.するとプロンプトを投げる画面に行きます
image.png

3.数分待つとフロントが出来上がっています。すげー
image.png

コード閲覧モードに入ると実際のコードが読めます。
image.png

フロントだけで完結するのならこのプロジェクトをインストールしてデプロイするだけでポートフォリオサイトが完成します。

ですが今後継続的に成果物を追加することを考えると毎度プロジェクトを開いて結果を書き込むのは面倒なのでNotion APIを使ってお手軽にポートフォリォに作品を追加できるようにします。

ルーティング

ページ遷移に react-router-domを使用してフロントエンドでルーティングを定義しています。

 <Routes>
  <Route path="/" element={<Home />} />
  <Route path="/projects" element={<AllProjects />} />
  <Route path="/career" element={<Career />} />
  <Route path="/events" element={<Events />} />
  <Route path="/tech-stack" element={<TechStack />} />
</Routes>

NotionのDBを参照する

今回のポートフォリオサイトでは、成果物の情報をコードに直接書くのではなく、Notionのデータベースで管理する構成にしました。

今回は以下のDBからデータを取得することを目的に使いました

image.png

Notion APIキーの取得方法を書こうと思ったのですが、他の方の記事に詳しく書いてあったのでそちらのリンクを張っておきます。
https://qiita.com/ulxsth/items/3434471ac91f8fa311cf

Notion APIへのアクセスには、Notion公式のJavaScript SDKである @notionhq/client を使用しました。

環境変数を読み込む

Notion Integrationで発行されたAPIキーをNOTION_API_KEYの値に。
NOTION_DATABASE_IDはNotionのDBから取得。

NOTION_API_KEY=secret_xxxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Notion APIを叩く

import express from "express";
import cors from "cors";
import { Client } from "@notionhq/client";
import dotenv from "dotenv";

dotenv.config();

const app = express(); //サーバー作成
app.use(cors()); //どこかでもアクセスを許可する。これはほんとに必要?

const notion = new Client({ auth: process.env.NOTION_API_KEY });

app.get("/api/events", async (req, res) => {
  const response = await notion.databases.query({
    database_id: process.env.NOTION_DATABASE_ID,
  });
  res.json(response.results);
});

app.listen(3002, () => {
  console.log("サーバー起動: http://localhost:3002");
});

Notionクライアントを作成する

Notion Apiを使うためにClientを使ってNotionクライアントを作成します

const notion = new Client({ 
    auth: process.env.NOTION_API_KEY 
});

auth には、Notion Integrationで発行されたAPIキーを指定します。

Notionデータベースを取得する

Notionデータベースの内容を取得するにはnotion.databases.query()を使用します。

const response = await notion.databases.query({ 
database_id: process.env.NOTION_DATABASE_ID, 
});

database_idには参照したいNotionデータベースのIDを指定します。

取得した結果は response に入ります。

Notion APIのレスポンスには、データベース内のページ情報だけでなく、次のデータがあるかどうかを示す情報なども含まれています。

今回フロント側で使いたいのは、DBに登録されている成果物の一覧なので、response.results のみをJSONとして返すようにしました。

res.json(response.results);

これにより、フロントエンド側では /api/events にアクセスするだけで、Notion DBの中身を取得できます。

フロントエンド側でデータを取得する方法

フロントエンド側では、fetch() を使ってExpressで作成した /api/events にアクセスします

const res = await fetch("/api/events");
const data = await res.json();

ここで取得できる data には、Express側から返した response.results が入っています。

Notion APIから返ってくるデータはそのままだと構造が複雑なので、画面で使いやすい形に変換します。

const formatted = data.map((item: any) => ({
  title: item.properties.タイトル.title[0]?.plain_text,
  date: item.properties.日付.date?.start,
  description: item.properties.プロジェクトについて.rich_text[0]?.plain_text,
  tags: item.properties.タグ.multi_select.map((tag: any) => tag.name),
  image: item.properties["ファイル&メディア"].url,
  github: item.properties.github.url,
  report: item.properties.記事.url,
}));

例としてNotion側のタイトルは以下のような形で取得しています。

item.properties.タイトル.title[0]?.plain_text

タグは multi_select なので、配列として取得し、map() を使ってタグ名だけを取り出しています。

item.properties.タグ.multi_select.map((tag: any) => tag.name)

このようにNotionのプロパティの種類によってデータの取得方法が変わる点に注意が必要でした。

取得したデータをstateに保存する

取得したデータは、Reactのstateに保存します。

setProjects(formatted);

その後、projects.map() を使ってカード形式で表示しました。

処理の流れ

React
↓
fetch("/api/events")
↓
Expressの /api/events
↓
notion.databases.query()
↓
Notion Database
↓
response.results をJSONで返す

の流れです

実装して感じた事

Notion DBからデータを取得する構成にしたことで、成果物を追加するときにコードを直接編集する必要がなくなりました。楽ができます。

おわり

現在は一部しかレスポンシブ対応していないので対応させたいですね。

figmaMakeを初めて利用したのですが、フロントのデザインに限らずコードまで作成してくれた驚きでした。
楽ができます。

普段バイブで開発をしていて実装力が落ちているように感じていたので今回、AIを使わずに基礎の復習をしたのですが、昔を思い出せて楽しかったです。

実際に作成したポートフォリオサイト
https://wasijayo.site

参考にした記事
https://qiita.com/ulxsth/items/3434471ac91f8fa311cf
ドキュメント
https://developers.notion.com/

Notion APIのバージョン2025-09-03以降ではdatabase_idではなくdata_source_idを使用する形が推奨されています。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?