Next.jsでAtomic Designを導入してみた話
背景
私は現在、Webアプリ開発を趣味にしている大学4年生です。
Next.jsでWebアプリを作る際のディレクトリ構成・設計に中々満足せず、色々と悩んでいたところ、某企業のインターンに参加した際、Atomic Designを導入したフロントエンドディレクトリ構成に非常に納得をしました。
その後実際にAtomic Designを導入したプロジェクトを作成しました。拙い部分が多々ありますが、ご容赦ください。
この記事では、実際にAtomic Designを導入した経験を踏まえ、Atomic Designを用いた設計の利点・欠点についてお話しします。
Atomic Designとは
Atomic Designとは、WebアプリやUIの構造を整理するための設計手法です。
小さなUIを組み合わせて、一つのページを作ることを目的にします。
そのために、コードを5段階に階層化し、それぞれを組み合わせる特徴があります。
この手法を取り入れることにより、コードの再利用性・保守性を高めることができます。
Atoms
UIを最も細かく分解したパーツです。これ以上分解することができない機能を提供します。
他のどの機能にも依存しないよう、コードを記述します。
Button, Input Fieldなどがここに当たります。
Molecules
Atomsを使い、ある程度まとまった機能を提供します。
たとえば、Input FieldとButtonを組み合わせ、検索Boxを作成する、などします。
基本的に、Stateなどは上から流すようにします。
Organisms
Atoms, Moleculesを使い、まとまった機能を提供します。
Headerなどがここに当たります。
Moleculesとの違いは、ドメインが入っているか否かです。
ドメインを入れることができるため、APIなどを使用するのはこの階層からになります。
Template
実際のページの枠組み(骨組み)を作ります。
フロントエンドのUIでいうと、ワイヤーフレームのような機能を提供します。
実際のロジック部分は、後述するPageで挿入します。
Page
実際に表示するページを作成します。
基本的にTemplateを用い、ワイヤーフレーム部分にOrganisms, Molecules, Atomsなどを入れ込み、ページ本体を提供します。
開発環境
続いて、私が実際に作成したプロジェクトを踏まえてお話しします。
ここでは開発環境を示します。
- Next.js 14.2.11
- Storybook 8.3.0
- Sass 1.78.0
プロジェクトのディレクトリ構造
前述したプロジェクトのディレクトリ構造について記述します。
├── atom
│ ├── button
│ │ ├── button.module.scss
│ │ ├── index.tsx
│ │ └── stories.tsx
│ ├── card
│ │ ├── card.module.scss
│ │ ├── index.tsx
│ │ └── stories.tsx
│ ├── skill-card
│ │ ├── hook.ts
│ │ ├── index.tsx
│ │ ├── skill-card.module.scss
│ │ └── stories.tsx
│ └── slider
│ ├── index.tsx
│ ├── slider.module.scss
│ ├── stories.tsx
│ └── style.css
├── molecules
│ ├── card
│ │ ├── about-card
│ │ ├── project-card
│ │ └── skill-card
│ └── skill-card-list
│ ├── data.json
│ ├── index.tsx
│ ├── skill-card-list.module.scss
│ └── stories.tsx
├── organisms
│ ├── cards
│ │ ├── index.tsx
│ │ └── stories.tsx
│ ├── experience
│ │ ├── index.tsx
│ │ └── stories.tsx
│ ├── footer
│ │ ├── footer.module.scss
│ │ ├── index.tsx
│ │ └── stories.tsx
│ ├── header
│ │ ├── atom
│ │ ├── header.module.scss
│ │ ├── index.tsx
│ │ └── stories.tsx
│ └── top-header
│ ├── index.tsx
│ └── top.module.scss
├── pages
│ ├── about
│ │ ├── about-me.json
│ │ ├── about.module.scss
│ │ ├── experiences.json
│ │ └── page.tsx
│ ├── product
│ │ ├── hook.ts
│ │ ├── index.tsx
│ │ ├── product.module.scss
│ │ ├── products.json
│ │ └── stories.tsx
│ └── skill
│ ├── data.json
│ ├── index.tsx
│ └── skill.module.scss
└── template
├── page-template
│ ├── index.tsx
│ ├── page-template.module.scss
│ └── stories.tsx
└── product-detail-template
├── index.tsx
├── product-detail-template.module.scss
├── products.json
└── stories.tsx
Atomic Designの利点
疎結合なコンポーネント化への意識が高まる
Atomic Designは、コンポーネント駆動開発と非常に相性が良いと感じています。
実際にAtomic Designを導入すると、UIをどのようにコンポーネント化しようか、という視点を持つことができ、コンポーネント同士を疎結合な状態にすることが容易です。
疎結合なほど、1つ1つのコンポーネントの責任が小さくなり、依存関係が絡み合うのを防ぐことができます。
車輪の再開発を防げる
細かく分解したパーツを使い回す性質上、同じパーツを何回も使うことになります。
これにより、再利用性が高まり、同じようなコード・ロジックを記述してしまう、などの車輪の再開発を防ぐことができます。
保守性が上がる
どこかのページで問題が発生した場合、その問題箇所を簡単に深ぼって分析することができます。これは細かくコンポーネント化したことにより、小さいスコープで問題を捉えることができるためです。
これにより、保守性の向上に貢献できます。
Atomic Designの欠点
コンポーネント間の依存関係が見えにくい
私の実装では、例えばOrganisms内でコンポーネントをimportすることがあったりなど、同じ階層のコンポーネントを使用することがありました。
そのため、コンポーネントの依存関係が見えにくく、手戻りが発生した際にコードを追う必要に迫られた場面がありました。
私は今回個人開発のため、かなり直感的にコンポーネントを分割したのですが、依存関係が発生しないよう、コードを書く前に、より詳細にコンポーネント分割方法を吟味しておくのが良いと考えます。
ドメイン同士の関係に対する意識が低くなる
Organismsにドメインが入ったコンポーネントを記述することはしますが、ドメイン同士を切り分けることができていません。
今回私が作ったプロジェクトでは、ドメインが特になく、さほど困ることはなかったです。しかし、大規模なプロジェクトになり、ユーザードメイン、商品ドメインなどがある場合、この実装だとドメイン同士が混ざり、非常に使いづらいディレクトリ構成になると思います。
クリーンアーキテクチャの中にAtomic Designを取り入れてディレクトリ構成を考える、などをすると、その問題を解決できるのではないか、と考えます。
コンポーネントの単位の統一がしにくい
個人開発なのでさほど困りませんでしたが、正直AtomsとMoleculesの違いが自分でも曖昧です。
ことこれがチーム開発だと、「どっちのディレクトリで作ればいいんだ」と議論の火種になるかもしれません。
汎用的ならば、Atoms, Moleculesに入れ、そうでなければOrganismsに入れる、程度の粒度でも良いのではないか、と思いました。
終わり
今回は、Next.jsにAtomic Designを導入して開発した話を書きました。
Atomic Designは、コンポーネントを意識する開発において非常に強い力を発揮します。
一方で、ドメインに対する意識が低いなどの欠点もあるため、他のアーキテクチャと組み合わせて使うことにより、より真価を発揮すると考えます。
私自身、設計について勉強している途中なので、また美しいと感じる設計に出会ったら、文章に起こそうと思います。
拙い文章をここまで読んでくださり、誠にありがとうございます。
みなさんがAtomic Designを取り入れようとした際の参考になれば幸いです。
Discussion
コンポーネントの単位に関して、私は以下のように定義するようにしています。
Atoms: 単一のHTMLタグに相当するもの(PropsにHTML属性を全て継承する)
Molecules: ビジネスドメインに依存しないUI
Organisms: ビジネスドメインに依存したUI
Templates: presentaional/containerのpresentaional
Pages: presentational/containerのcontainer
Atomsを単一のHTMLタグとしているのは、拡張性を保ちつつ再利用性を高めるためです。様々なUIを作る上で似ているが詳細は異なるUIがあります。このようなUIを実装する際は基本的に神コンポーネントを作らないためにビジネスドメインが異なるのであれば分けるべきだと思います。しかし、保守コスト抑制のためにできる限り再利用したい。この場合にAtomsが再利用の最小粒度として機能するために制約として単一のHTMLタグとして、全ての属性をPropsに渡すことがいいと考えています。
コメントありがとうございます!
結論から書きますと、y2kさんのコメントに「なるほど!」と納得しました。
記事では言葉不足だったので、以下に少し補足しながら詳細に書かせていただきます。
私の認識では、Atomsは「これ以上細かく分解出来ないUI」で、ある程度まとまったコンポーネントを提供するものだと考えていました。
y2kさんが書いてくださった「単一のHTML要素」よりは、「複数のHTMLで構成された一つの部品」というイメージを持っています。
このようにしている理由は2つあります。
ですが、y2kさんの書いてくださった内容を見て、h1タグ、pタグなどでそれぞれフォントサイズやmarginなどをAtomsで作っておいて、それをMolecules, Organismsなどで使う、というようにすれば、ある程度ファイル数を抑えつつ再利用性を高めれるな、と考えました。
勉強になります。ありがとうございます!
Do you have an example of the project structure as you described?
プロジェクト構造の例はありますか?
I do not have an example to present as documentation. Instead, I will describe the Next.js configuration that I am currently envisioning.
I have a plan to change my personal website to this structure. I will work out the details at that time.
@krisna
Thank you for your question!
It's not perfect, but you may find the repository I created at the following URL helpful: https://github.com/YutaK1026/kawaport
@y2k
ありがとうございます!質問者ではありませんが、私も参考にさせていただきます!