Javascriptフレームワーク(Astro)で効率的なUI開発を実現する方法

2025.04.01
目次

Web制作において、昨今はUI/UXやアクセシビリティの観点が強くなってきています。

企業やサービスのカラーを出す部分と、機能やページとして使いやすい状態を実現するため、JSフレームワークの利用やUIフレームワークをうまく利用することで高度な画面も効率的に構築していくことができるようになってきています。

実際のプロジェクトでは、デザインコンセプトやサービスの特色ごとにカスタマイズを求められることが多く、再利用可能なコンポーネントを活用することが鍵になります。

今回は実際のプロジェクトでも活用した、AstroとPrelineを利用した手法をご紹介できればと思います。

なぜAstroを選んだのか?

Astroは静的サイトジェネレーターでありながら、部分的にJavaScriptフレームワーク(ReactやVueなど)を組み込むことができます。
しかし、プロジェクトでの弊社のスコープはHTML、CSS、JavaScriptの開発であり、サーバーサイドはスコープ外のパターンです。

簡単にいうと、静的なUIと画面のHTMLを作成するという内容です。
その場合にはAstroは最適だと思います。※

  • 再利用性を高めるため各UIはコンポーネントとして開発したい。
    →レイアウト用のコンポーネント、UIコンポーネントを、共通のもの、フロント画面、管理画面などに分けて管理ができる。

  • 開発自体は高度であっても最終的なソースコードのJavaScriptは必要最低限にしたい。
    →Astroの機能は画面を組み上げるためだけにすることができ、必要なJavaScriptは外部化して読み込むようにできる。

  • ファイルの読み込みや状態による分岐を柔軟に行いたい。
    →Slot機能やページファイル自体をコンポーネントとして扱うことでPropsで必要な状態を静的に表現できる。

静的なHTMLでUI開発をする場合、上記のようなポイントが重要になりますが、AstroでTailwindCSSベースのPrelineを使用することが効率的に行うことができます。

※最終的にReactをベースとするアプリケーションの場合は、ReactとStoryBookを活用する方法があります。

UI開発の方針

今回のプロジェクトでは、以下のような開発方針を立てました。

  1. Prelineを活用し、ベースのUIコンポーネントを統一

  2. TailwindCSSをベースにスタイルを柔軟に調整しつつ、影響を最小限に

  3. Monorepo構成を採用し、複数の画面でコンポーネントを共有

  4. UIガイドページを作成し、エンジニアの認識を統一

それぞれ詳しく解説していきます。

1. Prelineを活用してUIの統一感を確保

Prelineは、TailwindCSSをベースにしたUIライブラリです。 カスタムCSSを追加する必要が少なく、コンポーネントのデザインが統一しやすいのが特徴です。 また、Figmaで利用できるデザインキットが提供されているため、デザイナーとの連携もスムーズに行うことができます。

例えば、Prelineのボタンコンポーネントは元々このような状態です。

<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none">
  Solid
</button>

<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 text-gray-500 hover:border-blue-600 hover:text-blue-600 focus:outline-none focus:border-blue-600 focus:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:border-neutral-700 dark:text-neutral-400 dark:hover:text-blue-500 dark:hover:border-blue-600 dark:focus:text-blue-500 dark:focus:border-blue-600">
  Outline
</button>

<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent text-blue-600 hover:bg-blue-100 hover:text-blue-800 focus:outline-none focus:bg-blue-100 focus:text-blue-800 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:hover:bg-blue-800/30 dark:hover:text-blue-400 dark:focus:bg-blue-800/30 dark:focus:text-blue-400">
  Ghost
</button>

<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-100 text-blue-800 hover:bg-blue-200 focus:outline-none focus:bg-blue-200 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-400 dark:hover:bg-blue-900 dark:focus:bg-blue-900">
  Soft
</button>

<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50 focus:outline-none focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700">
  White
</button>

<button type="button" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent text-blue-600 hover:text-blue-800 focus:outline-none focus:text-blue-800 disabled:opacity-50 disabled:pointer-events-none dark:text-blue-500 dark:hover:text-blue-400 dark:focus:text-blue-400">
  Link
</button>

それを次のようにカスタマイズしつつ、Astroコンポーネントとして作成できます。

---
const { type, color, disabled, className, ...props } = Astro.props;

const classBase =
  'px-4 py-1.5 rounded-lg inline-flex items-center justify-center text-admin-h5 tracking-normal font-semibold transition-opacity ease-in-out duration-200 hover:opacity-60';
const classColor = color === 'primary' ? 'bg-admin-primary text-white' : 'bg-admin-secondary text-admin-primary';
const classDisabled = disabled ? 'opacity-50 pointer-events-none' : '';
---
<button
  {...props}
  type={type === 'submit' ? `button` : type}
  class={`${classBase} ${classColor} ${classDisabled}`}
  class:list={[className]}
  disabled={disabled}
  aria-disabled={disabled}>
  <span>
    <slot />
  </span>
</button>

これを活用すれば、どの画面でも統一感のあるボタンを使用できますし、画面として微調整が必要な場合もCSSを追加することができます。

2. TailwindCSSをベースにしたスタイリングの柔軟性

TailwindCSSがベースになっていることで、UIのスタイルを統一しつつ、カスタムCSSの影響を最小限に抑えることができます。
コンポーネント単位で適用されるため、特定の要素だけにデザインを適用するのが容易になり、不要なCSSの肥大化をある程度防ぐことが可能です。
カスタマイズする際には、TailwindCSSのクラスとして追加を行ったり、CSSのカスタムプロパティでデザインを調整することができます。

例えば、tailwind.configで管理画面向けのテキストサイズを定義し、それをコンポーネントで利用することができます。

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontSize: {
        'admin-h1': '2.5rem',
        'admin-h2': '2rem',
        'admin-h3': '1.75rem',
        'admin-h4': '1.5rem',
        'admin-h5': '1.25rem',
        'admin-h6': '1rem',
      },
    },
  },
};
<h1 class="text-admin-h1">Heading 1</h1>
<h2 class="text-admin-h2">Heading 2</h2>
<h3 class="text-admin-h3">Heading 3</h3>
<h4 class="text-admin-h4">Heading 4</h4>
<h5 class="text-admin-h5">Heading 5</h5>
<h6 class="text-admin-h6">Heading 6</h6>

TailwindCSSを利用することで、CSSを一から書く手間を省き、柔軟にスタイリングを行うことができます。
また、CSSカスタムプロパティを追加することで、デザインのカスタマイズも容易に行うことができます。

3. Monorepo構成で複数の画面に対応

複数のプロジェクトでコンポーネントを統一するため、Monorepo構成を採用しました。
これにより、異なるアプリケーション間でコンポーネントを共有し一貫性を保つことができます。

Monorepoの利点

  • 開発効率の向上:共通コンポーネントを単一のリポジトリで管理することで、メンテナンスが容易に。

  • 依存関係の管理が容易:npm workspacesを活用し、パッケージの管理を効率化。

  • CI/CDの最適化:変更があったパッケージのみビルド・デプロイすることで、処理時間を短縮。

ディレクトリ構成例

project-root/
  ├── admin/
  ├── front/
  └── ui/

この構成により、管理画面(admin)とフロントサイト(front)で同じUIコンポーネントを共有でき、 tsconfig.jsonに以下の設定を追加することで@ui/*でUIコンポーネントを参照できるようになります。

"paths": {
  "@/*": ["src/*"],
  "@ui/*": ["../ui/src/*"]
}

Monorepo構成を採用し、複数の画面でコンポーネントを共有する仕組みを導入しました。
Monorepoじゃない場合には、変更が入るたびに同じ仕組みのUIを全て書き換える必要があったり、管理画面とフロント画面での差を理解して書き換える必要がありますが、共通コンポーネントとして管理することで、画面数が多くても開発の効率を落とさずに対応することができるようになります。

4. UIガイドページの作成

UIガイドページはuiディレクトリ配下に作成し、そこに各コンポーネントを読み込んで、バリエーションを一覧表示する仕組みを構築しました。

ガイドページの目的

  • コンポーネントの再利用を促進:開発者が容易にコンポーネントを発見・利用できるように。

  • デザインの統一を確保:UI仕様を明確化し、デザイナーとエンジニアの認識を一致させる。

  • 組み込み時のドキュメントとして:サーバーサイドへの組み込み時に、コンポーネントの使われ方やHTML構造を確認できるように。

ソースコード表示コンポーネントの設計

しかし、ガイドページを作るとなると、ただコンポーネントを並べるだけではなく、HTML化されたソースコードを表示する必要があります。 そのため、以下のコンポーネントを用いることで、UIコンポーネントの展開後のHTMLをサンプルとして提示させるようにしました。

---
const html = await Astro.slots.render('default');
import { Code } from 'astro/components';
import prettier from 'prettier';

// data-astro属性を排除
const rawHtml = html.replace(/ data-astro-[^=]+="[^"]*"/g, '');
const formatHTML = async (html: string) =>
  await prettier.format(html, {
    parser: 'html',
    singleAttributePerLine: true,
    htmlWhitespaceSensitivity: 'ignore'
  });

// コピー用CSSクラスで使えるユニークな文字列を生成
const generateRandomString = () => {
  return Math.random().toString(36).substring(2, 15);
};
const preId = generateRandomString();
---

<div class="w-full mx-auto">
  <Fragment set:html={html} />
</div>

<div class="relative w-full mx-auto mt-5">
  <div class="bg-gray-900 text-white p-4 rounded-md">
    <div class="flex justify-between items-center mb-2">
      <span class="text-gray-400">Code:</span>
      <button
        class="code bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1 rounded-md"
        data-clipboard-target=`.pre-${preId} code`
      >
        Copy
      </button>
    </div>
    <div class="overflow-x-auto">
      <Code
        code={await formatHTML(rawHtml)}
        lang="html"
        class=`rounded p-4 pre-${preId}`
      />
    </div>
  </div>
</div>

<script
  is:inline
  src="<https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.min.js>"
></script>
<script is:inline>
  window.addEventListener('DOMContentLoaded', () => {
    new ClipboardJS('.code');
  });
</script>

ポイントは、Astroの組み込みコンポーネントとしてCodeというソースコードを表示ができるものを使用していることと、コードのフォーマットにはPrettierを使用しコードの見やすさを保つようにしていることです。

開発環境にPrettierを入れることは当たり前のようにあると思いますが、こういった画面上のソースコードを整形する場面でも使用することができます。

StoryBookを使用したらいいのでは?と思うかもしれませんが、現状Astroは公式のライブラリのみでAstroコンポーネントにStoryBookを使用することが難しいため、Astroの機能を活かしてUIガイドページを作成しました。

また、今回ガイドとして提示したいのはAstroのコンポーネント自体のソースコードではなく生成後のHTMLになりますので、用途としてもこのようにガイドを作成する必要があります。

実際のコンポーネントを読み込ませることで、最低限のソースコードのみでガイドを作ることができました。

このガイドにより、フロントエンドはコンポーネントをカタログ的に確認することができ、サーバーサイドもHTML構造を把握するために使用できますし、フロントエンドエンジニアを必要としない画面改修があった場合に必要なHTMLをコピーしてページへ追加することも想定しています。

まとめ

今回、AstroとPrelineを活用することで、UI開発を効率化することができました。 UIの統一感を保ちつつ、柔軟にスタイリングを行うことができ、Monorepo構成でコンポーネントを共有することで、開発効率を向上させることができますし、UIガイドページを作成することで、コンポーネントの再利用を促進し、デザインの統一を確保することができました。

こういったパターンでの開発は、今まで静的にさまざまな苦労を経て管理、構築されていたと思いますが、Astroを活用することで、より効率的にUI開発を行うことができるようになると思います。

Contact

制作のご依頼やサービスに関するお問い合わせ、
まだ案件化していないご相談など、
お気軽にお問い合わせください。

お問い合わせはこちら

関連ブログ