Docs / tech

利用する関連パッケージのインストール

2025-11-16 2025-11-29 |

前回は Next.js のトップページでブログのタイトルを取得するところまで進めました。今回は、ブログを作っていくにあたって手持ちのライブラリなどを追加して環境を整えて行きます。

アイコンライブラリの追加

今回、サイトで利用するアイコンは Lucide React を利用していきます。

以下のコマンドを実行してください。

npm i lucide-react

他にも様々なライブラリアがあります。好みにあわせて利用してください。

Storybook を利用できるようにする

コンポーネントの管理で便利な Storybook をこのプロジェクトでも利用できるようにして行きます。なお、手順は以下のページを参考にして進めて行きます。

Next.js + Storybook

以前の記事を参考にする場合ですが、当時は postcss.config.mjs の記述でエラーが出ていましたが、Next.js のバージョンがあがって改善しています(以前の記事にも同じ注意書きを入れておきました)。

Storybook の追加

Next.js のプロジェクトのルートで、以下のコマンドを実行します。

npm create storybook@latest

インストールが完了すると、storybook が自動的に起動します。次回以降は以下のコマンドで起動します。

npm run storybook

これで起動するようになりました。

Tailwind CSS のファイルの変更

Next.js で利用している Tailwind CSS のファイルは apps/nextjs/app/globals.css に展開されています。これを、apps/nextjs/styles/globals.css に移動します。

- apps
  - nextjs
    - app
      - globals.css 元のファイルの位置
    - style
      - globals.css 新しい位置

Next.js でこのファイルを参照しているコードを変更します。

apps/nextjs/app/layout.tsx
// import "./globals.css";
import "@/styles/globals.css";

また、Storybook の preview.ts のファイルも上記の同じファイルを参照するようにします。

apps/nextjs/.storybook/preview.ts
import type { Preview } from '@storybook/nextjs-vite'
import "@/styles/globals.css";

const preview: Preview = {
  parameters: {
    controls: {

コンポーネントと CSS を整理する

続いて Storybook がサンプルとして提供しているファイルを削除して人まずサンプルのコンポーネントを配置します。

まず、Storybook のサンプルとして入っているコンポーネントのフォルダ名を apps/nextjs/stories から apps/nextjs/components に変更します。また、stories.tsも一括で管理するために、storybook のフォルダを作成します。

- apps
  - nextjs
    - components 新しいコンポーネントのファイルの保存場所
    - stories このフォルダを上のフォルダ名に変更
    - storybook stories.ts ファイルの保存場所

コンポーネントの参照場所が変わるため、以下のファイルで参照しているパスを変更してください。

apps/nextjs/.storybook/main.ts
import type { StorybookConfig } from "@storybook/nextjs-vite";

const config: StorybookConfig = {
  stories: [
    "../components/**/*.mdx",
    "../storybook/**/*.stories.@(js|jsx|mjs|ts|tsx)",
  ],

続いてサンプルのファイルを配置して行きます。コンポーネントのファイルとして以下のコードを利用してください。

apps/nextjs/components/Hero.tsx
import { Calendar, CalendarSync } from "lucide-react";

export interface HeroProps {
	/** Title */
	title: string;
	/** publish date */
	publishDate?: string;
	/** updated date */
	lastUpdated?: string;
	/** Option: Image */
	imageUrl?: string;
	/** Option: Tags */
	tags?: HeroTags[];
}

export interface HeroTags {
	/** Tag name */
	name: string;
	/** Tag url */
	url: string;
}

export default function Hero(props: HeroProps) {
	const title = props.title || "No Title";
	const publishDate = props.publishDate || "2023-01-01";
	const lastUpdated = props.lastUpdated || undefined;
	const imageUrl = props.imageUrl || undefined;
	const tags = props.tags || [];

	return (
		<header className="component hero">
			<div
				className="image"
				style={{
					backgroundImage: `url('${
						imageUrl || "/placeholder.svg?height=600&width=1200"
					}')`,
				}}
			></div>
			<div className="absolute inset-0 bg-black/50" />
			<div className="tags">
				{tags.map((tag, index) => (
					<a key={index} href={tag.url} className="tag">
						{tag.name}
					</a>
				))}
			</div>
			<div className="title">{title}</div>
			<div className="publishDate">
				<Calendar />
				{publishDate}
				{lastUpdated && (
					<>
						<span className="mx-2">|</span>
						<CalendarSync />
						{lastUpdated}
					</>
				)}
			</div>
		</header>
	);
}

続いて、stories のファイルを作成します。

apps/nextjs/storybook/Hero.stories.ts
// components/Card.stories.ts
import type { Meta, StoryObj } from "@storybook/nextjs-vite";
import Hero from "@/components/Hero";

const meta: Meta<typeof Hero> = {
	title: "Components/Hero",
	component: Hero,
	tags: ["autodocs"],
};

export default meta;
type Story = StoryObj<typeof Hero>;

export const Default: Story = {
	args: {
		title: "Sample Hero",
		publishDate: "2024/04/16",
		imageUrl: "/ludemeula-fernandes-9UUoGaaHtNE-unsplash.jpg",
		tags: [
			{ name: "Tag1", url: "#" },
			{ name: "Tag2", url: "#" },
		],
	},
};

export const WithLastUpdate: Story = {
	args: {
		title: "Sample Hero",
		publishDate: "2024/04/16",
		lastUpdated: "2024/04/17",
		imageUrl: "/ludemeula-fernandes-9UUoGaaHtNE-unsplash.jpg",
		tags: [
			{ name: "Tag1", url: "#" },
			{ name: "Tag2", url: "#" },
		],
	},
};

最後にスタイルシートを調整します。スタイルシートのファイルを1つ追加します。

apps/nextjs/styles/hero.css
.component.hero {
	@apply relative w-full h-[45vh] min-h-[400px] overflow-hidden;
}

.component.hero .image {
	@apply absolute inset-0 bg-cover bg-center bg-no-repeat;
}

.component.hero .title {
	@apply relative z-10 mt-5 text-3xl md:text-4xl font-bold leading-tight text-white text-center;
}

.component.hero .publishDate {
	@apply relative z-10 mt-5 flex items-center gap-2 text-sm md:text-base text-gray-200 justify-center;
}

.component.hero .tags {
	@apply relative z-10 flex flex-wrap gap-2 mt-30 text-gray-200 justify-center;
}

.component.hero .tag {
	@apply inline-block px-3 py-1 text-black bg-gray-200 rounded-full text-sm hover:bg-gray-400 transition-colors;
}

上記のスタイルのファイルを globals.css に反映させます。他のコードを削除して、以下のようにします。

apps/nextjs/styles/globals.css
@import "tailwindcss";
@import "./hero.css";

今回、サンプルの画像を public に追加します。画像ファイルに関しては、apps/nextjs/storybook/Hero.stories.ts にてパスを記載しているため、任意のファイルをアップした場合は画像のパスを変更してください。

動作確認

上記の変更がどのように適用されるか確認をして行きます。

Storybook の確認

まず Storybook での動作確認です。以下のコマンドを実行してください。

npm run storybook

以下のように Hero コンポーネントがサンプルとして表示できています。

Next.js での確認

続いて Next.js で動作確認をします。まだ今回のコンポーネントを追加していないため、トップページに Hero を追加します。

まずコンポーネントを読み込みます。

apps/nextjs/app/page.tsx
import Hero from "@/components/Hero";

続いてこのコンポーネントを利用するために、同じファイルに Hero のあたりを追加してください。

apps/nextjs/app/page.tsx

export default async function IndexPage() {
  const posts = await client.fetch<SanityDocument[]>(POSTS_QUERY, {}, options);

  return (
    <main className="container mx-auto min-h-screen max-w-3xl p-8">
      <Hero
        title="Welcome to My Blog"
        publishDate="2025/11/16"
        imageUrl="/ludemeula-fernandes-9UUoGaaHtNE-unsplash.jpg"
      />

これで準備が完了です。 Next.js のコンポーネントを起動します。

npm run dev

以下のように、コンポーネントを利用することができました。

まとめ

これからブログを作っていくにあたって、コンポーネントの管理ができると便利なので、取り急ぎ Storybook を追加しました。またサイトで利用するアイコンのライブラリも最初に追加しており、さっそく Hero のところでアイコンとして利用しています。

ここまでのコードは、以下のブランチで公開しています。