TypeScript × Next.jsで作るマルチツールアプリ 〜型安全・UI統一・機能追加まで全部TypeScriptでやってみた~
1.はじめに
「毎日使いたい開発ツールを1ヶ所にまとめたい!」
そんな思いから、WebアプリをTypeScript+React+Next.jsで開発しました。
このWEBアプリでは複数の開発系ツールをまとめて提供しています。
- マークダウンエディタ
- JSONフォーマッター
- XMLフォーマッター
- パスワードジェネレーター
- QRコードジェネレータ
- JSON⇔YAMLコンバーター
- CSV⇔JSONコンバータ
- サイトマップジェネレータ
- テキストカウンター
※現時点では機能数はまだ少なく今後追加予定です🙇♂️
本WEBアプリや共通レイアウトは下記リポジトリに公開しております。レイアウトサンプル
■ なぜ作ったのか?
開発現場で「サクッと使えるWebツール」があると便利だけど、以下のような悩みを感じていました。
- 色々なサイトに機能が分散している
- サイトが分散しているのでUIに統一感がなく操作感が悪い
- 広告や余計な機能だらけ
そこで「自分が本当に欲しい、誰でも手軽に使えるWebツール」を
TypeScriptで型安全&メンテナブルに実装してみました。
■ TypeScriptを選んだ理由
WEBアプリを公開する上で、私のお財布事情と相談した結果、コスト削減のためGitHub Pagesでの静的公開を前提としていました。
GitHub Pagesで静的公開するという方針上、バックエンドやAPIサーバーを持たず、
各ツールのデータ処理・変換ロジックは全てフロントエンド(TypeScript)で完結させています。
なぜTypeScriptを選んだのか?
- 型定義により、ツール間で一貫した使いやすさと予期せぬバグの回避が実現できる
- 全てがフロントエンドで自己完結するので、ランタイムエラーや想定外の入力もTypeScriptの型チェックで事前に防ぐことができる
- 型安全のおかげで、複雑なデータ変換やバリデーションも安心して実装できる
上記のように「フロントエンド=TypeScriptだけでロジックからUIまで管理」という構成が、
今回の開発においてTypeScriptの恩恵を最大限に受けられるスタイルだと強く実感しました。
2.型定義+定数化の恩恵
ここでは、型定義と定数化による集中管理の恩恵について紹介します。
「フロントエンド=TypeScriptだけでロジックからUIまで管理」している都合上、
以下のようにTypeScriptの型+定数化による集中管理を多用しています。
// src/constants/tools.ts
export type Tool = {
id: string;
name: string;
name_en: string;
description: string;
description_en: string;
path: string;
icon: React.ComponentType<any>;
};
export const TOOL_LIST: Tool[] = [
{
id: "markdown-editor",
name: "マークダウンエディタ",
name_en: "Markdown Editor",
description: "リアルタイムプレビュー付きのMarkdownエディタです。",
description_en: "A Markdown editor with real-time preview.",
path: "/markdown-editor",
icon: Blocks,
},
...以下略
];
こんなの当たり前じゃんとは思うかもしれませんが、
型定義+定数化による恩恵は想像以上に大きいと実感しました。
⭐ 必須項目・データ構造の「抜け漏れ」を未然に防げる
型で定義されたプロパティが揃っていないと即エラーとなり、
新規機能や画面追加時に「指定忘れ」や「タイプミス」がすぐ分かりました。
⭐ 正しい値だけを使わせることで、バグを未然に防止
型制約により、不正な値(例: 想定外のパスや数値型に文字列を代入など)があると検知することができました。
⭐ 定数リスト管理による自動UI生成・一元管理がしやすい
画面で表示しているUIコンポーネントも型定義+定数化することで、
追加や削除も「定数を直すだけ」でUIに一斉反映できました。
3.型定義+共通部品化な設計の恩恵
ここでは、型定義+共通部品化な設計の恩恵について紹介します。
機能毎に画面を作ると「見た目を統一したい」「propsの型を使い回したい」という課題が出ます。
そこで、各機能共通で使用する3ペイン構成のレイアウトを汎用コンポーネント化しました。
具体的には共通レイアウトの型を定義しておき、
// src/layouts/three-pane-layout.tsx
type ThreePaneLayoutProps = {
left: React.ReactNode;
centerTitle: string | React.ReactNode;
centerContent: React.ReactNode;
rightTitle: string | React.ReactNode;
rightContent: React.ReactNode;
};
レイアウトの型に沿ってページを作成するだけで、
簡単に新しいページを作成する仕組みを実現できました。
return (
<ThreePaneLayout
left={<AppSidebar />}
centerTitle="マークダウンエディタ"
centerContent={
<div className="flex flex-col h-full space-y-2">
<Textarea
className="flex-1 font-mono shadow-none focus:ring-0 focus:border-gray-300 dark:focus:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
placeholder="ここにMarkdownを書いてください。"
value={markdown}
onChange={(e) => setMarkdown(e.target.value)}
/>
</div>
}
rightTitle="プレビュー"
rightContent={
<div className="prose dark:prose-invert max-w-none">
<div className="prose dark:prose-invert max-w-none">
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]}>
{markdown}
</ReactMarkdown>
</div>
</div>
}
/>
);
}
もちろんここでもTypeScriptによる型の恩恵を実感します。
⭐ UIの一貫性を保ちやすくなる
ユーザ目線で考えると・・・
どのページも見た目/操作性が統一され、ユーザー体験の向上が期待できるかと思います。
⭐ 型によるpropsのチェックでバグを未然に防止
開発者目線で考えると・・・
例えば共通レイアウトやUI部品に対し、「必須のタイトルやコンテンツが抜けている」「型が違う」といったミスが型エラーですぐ分かりました。
また、型定義のおかげでUI部品の再利用やメンテナンスもラクラクになりました。
4.課題や工夫点
ここでは、実際の開発経験をベースに課題や工夫点を紹介します。
⭐ 型定義が肥大化しがち
どうしても型定義が肥大化しがちなので、types.tsで分割管理する必要があります。
ツール情報や共通propsの型が増えてきたタイミングで、型定義ファイルを分割して管理することで、型の見通しや保守性が向上するかと思います。
⭐ 外部ライブラリの型定義がない場合
例えば一部外部ライブラリで型定義が無い場合がありました。
その場合は
- まずは「@types/パッケージ名」 のような型定義パッケージを導入する
- どうしても見つからない場合は、自分で「declare module "ライブラリ名";」のように手動で最低限の型宣言を追加する
これにより、「型なし」で困ることは意外と少なくなると思いました。
⭐ コンポーネント再設計・型定義の柔軟な拡張
高頻度で「stringだけOKにしていたが、途中で別の型も必要に…」といったことが起こります。
型設計に慣れない間は「型エラー/エディタ補完/生成AIツール」を駆使して、
徐々に再設計や型定義の拡張をして対処するしかないと感じました。
5.おわりに
TypeScriptのおかげで「作る・直す・増やす」全ての工程で開発の安心感と開発体験の向上を実感できました。
そして、意外にもフロントエンドだけで色々な機能を開発できることに気づきました。
引き続きTypeScriptの強さを武器に開発したいと思います。
Discussion