言語を切り替える
テーマを切り替える

shadcn/uiで管理画面の骨組みを構築:Sidebar + Layout ベストプラクティス

スケーラブルな管理画面の骨組みをゼロから構築するステップバイステップガイド。SidebarコンポーネントからNext.js Layout統合まで、すぐに使える完全なコード付き。


先週、管理画面のプロジェクトを受注した際、最初に思いついたのがshadcn/uiでした。正直、Ant DesignやMUIを使ったことがありますが、スタイルのカスタマイズにはいつも苦労していました——大量のスタイルをオーバーライドするか、フレームワークの設計思想に縛られるかのどちらかでした。

shadcn/uiは違います。「Copy-paste」モデルで、コードがプロジェクトに直接置かれ、自由に変更できます。2週間使ってみて、本当に良いと感じました。特にSidebarコンポーネントとNext.js App Routerの組み合わせは、管理画面の骨組み構築が驚くほどスッキリしました。

この記事では、私の実践的な経験をまとめました。ゼロから始めて、スケーラブルな管理画面レイアウトを構築する方法を解説します。


1. なぜshadcn/ui Sidebarを選ぶのか?

まず、私が遭遇した問題から話しましょう。

以前、Ant Design Proを使っていました。すぐに使い始められるのは確かに便利ですが、プロジェクトが長くなると頭痛の種が出てきます:サイドバーのスタイルを変更するには、ドキュメントを何時間も探す必要があります;カスタムインタラクションを追加しようとすると、フレームワークの制限にぶつかります。MUIも同様の問題があります——テーマのカスタマイズは柔軟ですが、Material Designの書き方を知っていることが前提です。

従来のソリューションの問題点

Ant Design:機能は充実していますが、カスタマイズのコストが高い。サイドバーの角丸を変えたいだけでも、3層のスタイルオーバーライドが必要かもしれません。

MUI:完全なデザインシステムですが、学習曲線が急です。Styled Componentsの書き方は、新しいチームメンバーが習得するのに約1週間かかります。

自作:完全にコントロールできますが、レスポンシブデザイン、アクセシビリティ、キーボードナビゲーションをサポートするサイドバーをゼロから書くとなると、少なくとも3日はかかります。

shadcn/uiのアプローチ

shadcn/uiは別の道を選んでいます:

  • Copy-pasteモデル:コンポーネントコードがプロジェクト内に存在し、ブラックボックスの依存関係がない
  • Radix UIベース:アクセシビリティが組み込まれ、キーボードナビゲーションとARIA属性が処理済み
  • Tailwind CSS駆動:スタイルはクラス名だけで、自由に変更でき、スタイルオーバーライドの悩みがない

MUIからshadcn/uiに移行するチームをよく見かけますが、理由はシンプルです:彼らが求めているのはコントロールであって、すぐに使えるテンプレートではありません

適用シーン

もし以下を構築しているなら:

  • 中小規模の管理画面
  • SaaS製品のコンソール
  • 社内ツールや運用プラットフォーム

shadcn/ui Sidebarは試す価値があります。完全な管理画面テンプレートは提供しませんが、十分に柔軟な骨組みを提供します。


2. Sidebarコンポーネントアーキテクチャ

始める前に、shadcn/ui Sidebarのコンポーネントシステムを理解しましょう。この部分は公式ドキュメントで明確に説明されているので、簡単に触れます。

コアコンポーネント一覧

Sidebarは完全なコンポーネントセットで構成され、それぞれが独自の責任を持ちます:

SidebarProvider   // 状態コンテキスト、アプリ全体をラップ
Sidebar          // サイドバーコンテナ
SidebarHeader    // 固定上部エリア、Logo用
SidebarContent   // スクロール可能なコンテンツエリア、メニュー用
SidebarGroup     // メニューグループ
SidebarMenu      // メニューリスト
SidebarMenuItem  // メニューアイテム
SidebarMenuButton // メニューボタン(Link対応)
SidebarFooter    // 固定下部エリア、ユーザー情報用
SidebarTrigger   // 折りたたみ/展開ボタン
SidebarInset     // メインコンテンツラッパー

多くのコンポーネントに見えますが、関係は明確です:

SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   └── SidebarGroup
│   │       └── SidebarMenu
│   │           └── SidebarMenuItem
│   │               └── SidebarMenuButton
│   └── SidebarFooter
└── SidebarInset
    └── {children}

状態管理

Sidebarの折りたたみ状態はSidebarProviderが管理します。2つのモードがあります:

非制御モード(推奨):

<SidebarProvider defaultOpen={true}>
  <Sidebar />
</SidebarProvider>

制御モード

const [open, setOpen] = useState(true);

<SidebarProvider open={open} onOpenChange={setOpen}>
  <Sidebar />
</SidebarProvider>

ほとんどの場合、非制御モードで十分です。他の場所でSidebarの状態を制御する必要がある場合(ユーザー設定にトグルがあるなど)のみ、制御モードを使用します。

レスポンシブデザイン

Sidebarには組み込みのレスポンシブサポートがあります:

  • デスクトップ:左側に固定、SidebarTriggerで折りたたみ可能
  • モバイル:自動的にドロワー(Sheet)になり、Triggerをクリックすると表示

このロジックは内部で処理されるため、SidebarProviderで設定するだけで、コンポーネントが残りを処理します。


3. Next.js Layout統合実践

さて、コア概念は説明しました。次はメインイベント——SidebarをNext.jsのLayoutシステムに統合します。

3.1 プロジェクト構造設計

Next.jsのRoute Groupsを使用してレイアウトを整理することをお勧めします。これにより、異なるページが異なるレイアウトを持つことができ、URL構造に影響しません。

app/
├── layout.tsx              # Root Layout(グローバル)
├── (marketing)/            # マーケティングページグループ(Landing、About)
│   ├── layout.tsx          # Sidebarなし
│   └── page.tsx            # ホームページ
├── (dashboard)/            # 管理画面ページグループ
│   ├── layout.tsx          # Sidebar付きレイアウト
│   ├── page.tsx            # ダッシュボードメイン
│   ├── users/
│   │   └── page.tsx        # ユーザー管理
│   └── settings/
│       └── page.tsx        # システム設定
└── (auth)/                 # 認証ページグループ
    ├── layout.tsx          # 中央揃えレイアウト
    ├── login/
    │   └── page.tsx        # ログインページ
    └── register/
        └── page.tsx        # 登録ページ

この構造の利点:

  1. レイアウト分離:マーケティングページにはSidebarが不要、管理画面には必要——Route Groupsで自然に分離
  2. クリーンなURL(dashboard)はURLに表示されず、/usersはそのまま/users
  3. 拡張が容易:新しいページグループの追加はフォルダを作成するだけ

3.2 Root Layout設定

Root Layoutはアプリのエントリーポイントです。ここでグローバルな設定を行います:テーマ、フォント、SidebarProvider。

// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { SidebarProvider } from "@/components/ui/sidebar";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Admin Dashboard",
  description: "Built with shadcn/ui and Next.js",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;html lang="ja"&gt;
      &lt;body className={inter.className}&gt;
        &lt;SidebarProvider&gt;
          {children}
        &lt;/SidebarProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}

SidebarProviderをRoot Layoutに配置することに注意してください。Dashboard Layoutではなく。これにより、Sidebarの状態がページ間で永続化されます(例:/usersから/settingsに移動しても、折りたたみ状態が保持されます)。

3.3 Dashboard Layout実装

Dashboard Layoutは管理画面のコアレイアウトで、Sidebarをここで導入します。

// app/(dashboard)/layout.tsx
import { AppSidebar } from "@/components/app-sidebar";
import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar";
import { Separator } from "@/components/ui/separator";
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;&gt;
      &lt;AppSidebar /&gt;
      &lt;SidebarInset&gt;
        &lt;header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"&gt;
          &lt;SidebarTrigger className="-ml-1" /&gt;
          &lt;Separator orientation="vertical" className="mr-2 h-4" /&gt;
          &lt;Breadcrumb&gt;
            &lt;BreadcrumbList&gt;
              &lt;BreadcrumbItem className="hidden md:block"&gt;
                &lt;BreadcrumbLink href="/dashboard"&gt;
                  ダッシュボード
                &lt;/BreadcrumbLink&gt;
              &lt;/BreadcrumbItem&gt;
              &lt;BreadcrumbSeparator className="hidden md:block" /&gt;
              &lt;BreadcrumbItem&gt;
                &lt;BreadcrumbPage&gt;概要&lt;/BreadcrumbPage&gt;
              &lt;/BreadcrumbItem&gt;
            &lt;/BreadcrumbList&gt;
          &lt;/Breadcrumb&gt;
        &lt;/header&gt;
        &lt;main className="flex-1 p-4 pt-6"&gt;{children}&lt;/main&gt;
      &lt;/SidebarInset&gt;
    &lt;/&gt;
  );
}

このレイアウトには以下が含まれます:

  1. AppSidebar:カスタムサイドバーコンポーネント(次のセクション)
  2. SidebarInset:メインコンテンツラッパー、Sidebar折りたたみ時の幅を自動処理
  3. Header:上部ナビゲーションバー、SidebarTriggerとパンくずリストを含む
  4. Main:メインコンテンツエリア

3.4 AppSidebarコンポーネント実装

それでは、サイドバー自体を実装しましょう。設定駆動のアプローチをお勧めします:ナビゲーションメニューを設定ファイルに保存し、コンポーネントがそれに基づいてレンダリングします。

まず、ナビゲーション設定を定義します:

// lib/navigation.ts
import {
  Home,
  Users,
  Settings,
  FileText,
  BarChart3,
  Shield,
} from "lucide-react";

export interface NavItem {
  title: string;
  href: string;
  icon: React.ComponentType&lt;{ className?: string }&gt;;
  badge?: string;
}

export const navConfig: NavItem[] = [
  {
    title: "概要",
    href: "/dashboard",
    icon: Home,
  },
  {
    title: "ユーザー管理",
    href: "/users",
    icon: Users,
    badge: "12", // バッジ
  },
  {
    title: "データ分析",
    href: "/analytics",
    icon: BarChart3,
  },
  {
    title: "コンテンツ管理",
    href: "/content",
    icon: FileText,
  },
  {
    title: "システム設定",
    href: "/settings",
    icon: Settings,
  },
  {
    title: "権限管理",
    href: "/permissions",
    icon: Shield,
  },
];

次にAppSidebarを実装します:

// components/app-sidebar.tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { navConfig } from "@/lib/navigation";
import { Logo } from "@/components/logo";
import { UserNav } from "@/components/user-nav";

export function AppSidebar() {
  const pathname = usePathname();

  return (
    &lt;Sidebar&gt;
      &lt;SidebarHeader className="border-b border-border"&gt;
        &lt;Logo /&gt;
      &lt;/SidebarHeader&gt;

      &lt;SidebarContent&gt;
        &lt;SidebarGroup&gt;
          &lt;SidebarGroupLabel&gt;ナビゲーション&lt;/SidebarGroupLabel&gt;
          &lt;SidebarGroupContent&gt;
            &lt;SidebarMenu&gt;
              {navConfig.map((item) =&gt; {
                const isActive = pathname === item.href;

                return (
                  &lt;SidebarMenuItem key={item.href}&gt;
                    &lt;SidebarMenuButton
                      asChild
                      isActive={isActive}
                      tooltip={item.title}
                    &gt;
                      &lt;Link href={item.href}&gt;
                        &lt;item.icon className="h-4 w-4" /&gt;
                        &lt;span&gt;{item.title}&lt;/span&gt;
                        {item.badge && (
                          &lt;span className="ml-auto text-xs bg-primary text-primary-foreground rounded-full px-2 py-0.5"&gt;
                            {item.badge}
                          &lt;/span&gt;
                        )}
                      &lt;/Link&gt;
                    &lt;/SidebarMenuButton&gt;
                  &lt;/SidebarMenuItem&gt;
                );
              })}
            &lt;/SidebarMenu&gt;
          &lt;/SidebarGroupContent&gt;
        &lt;/SidebarGroup&gt;
      &lt;/SidebarContent&gt;

      &lt;SidebarFooter className="border-t border-border"&gt;
        &lt;UserNav /&gt;
      &lt;/SidebarFooter&gt;
    &lt;/Sidebar&gt;
  );
}

ここで重要なポイント:ルートハイライトusePathname()で現在のパスを取得し、item.hrefと比較します。一致した場合、isActive={true}SidebarMenuButtonに渡すと、コンポーネントがアクティブスタイルを自動的に適用します。

3.5 マルチレベルメニューの実装

管理画面にサブメニューがある場合、CollapsibleSidebarGroupをラップできます:

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ChevronDown } from "lucide-react";

// SidebarMenu内で
&lt;Collapsible defaultOpen&gt;
  &lt;SidebarMenuItem&gt;
    &lt;CollapsibleTrigger asChild&gt;
      &lt;SidebarMenuButton&gt;
        &lt;Settings className="h-4 w-4" /&gt;
        &lt;span&gt;システム設定&lt;/span&gt;
        &lt;ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" /&gt;
      &lt;/SidebarMenuButton&gt;
    &lt;/CollapsibleTrigger&gt;
    &lt;CollapsibleContent&gt;
      &lt;SidebarMenuSub&gt;
        &lt;SidebarMenuSubItem&gt;
          &lt;SidebarMenuSubButton href="/settings/general"&gt;
            &lt;span&gt;基本設定&lt;/span&gt;
          &lt;/SidebarMenuSubButton&gt;
        &lt;/SidebarMenuSubItem&gt;
        &lt;SidebarMenuSubItem&gt;
          &lt;SidebarMenuSubButton href="/settings/security"&gt;
            &lt;span&gt;セキュリティ設定&lt;/span&gt;
          &lt;/SidebarMenuSubButton&gt;
        &lt;/SidebarMenuSubItem&gt;
      &lt;/SidebarMenuSub&gt;
    &lt;/CollapsibleContent&gt;
  &lt;/SidebarMenuItem&gt;
&lt;/Collapsible&gt;

4. 応用機能の実装

基本レイアウトが整いました。次は実用的な応用機能を見ていきましょう。

4.1 ロールベースアクセス制御(RBAC)

多くの管理画面では、ユーザーのロールに応じて異なるメニューを表示する必要があります。実装は簡単です:ナビゲーション設定にrolesフィールドを追加し、レンダリング時にフィルタリングします。

まず、設定を更新します:

// lib/navigation.ts
export interface NavItem {
  title: string;
  href: string;
  icon: React.ComponentType&lt;{ className?: string }&gt;;
  roles?: string[]; // 許可されたロール
}

export const navConfig: NavItem[] = [
  {
    title: "概要",
    href: "/dashboard",
    icon: Home,
    // roles未設定 = 全員が閲覧可能
  },
  {
    title: "ユーザー管理",
    href: "/users",
    icon: Users,
    roles: ["admin", "manager"], // adminとmanagerのみ
  },
  {
    title: "権限管理",
    href: "/permissions",
    icon: Shield,
    roles: ["admin"], // adminのみ
  },
];

次に、AppSidebarでユーザーロールに基づいてフィルタリングします:

// components/app-sidebar.tsx
import { useAuth } from "@/hooks/use-auth"; // 認証フック

export function AppSidebar() {
  const pathname = usePathname();
  const { user } = useAuth(); // 現在のユーザーを取得

  const filteredNav = navConfig.filter((item) =&gt; {
    if (!item.roles) return true; // ロール制限なし = 全員
    return item.roles.some((role) =&gt; user?.roles?.includes(role));
  });

  return (
    &lt;Sidebar&gt;
      {/* ... */}
      &lt;SidebarMenu&gt;
        {filteredNav.map((item) =&gt; {
          // ...
        })}
      &lt;/SidebarMenu&gt;
      {/* ... */}
    &lt;/Sidebar&gt;
  );
}

これで、通常のユーザーがログインしても「権限管理」メニュー項目は表示されなくなります。

4.2 外部リンクと区切り線

サイドバーに外部リンク(ドキュメント、ヘルプセンターなど)や、メニューをグループ化する区切り線が必要な場合もあります。shadcn/ui Sidebarはこれもサポートしています:

&lt;SidebarGroup&gt;
  &lt;SidebarGroupLabel&gt;メイン機能&lt;/SidebarGroupLabel&gt;
  &lt;SidebarGroupContent&gt;
    &lt;SidebarMenu&gt;
      {/* メインメニュー項目 */}
    &lt;/SidebarMenu&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

&lt;SidebarGroup&gt;
  &lt;SidebarGroupLabel&gt;ヘルプ&サポート&lt;/SidebarGroupLabel&gt;
  &lt;SidebarGroupContent&gt;
    &lt;SidebarMenu&gt;
      &lt;SidebarMenuItem&gt;
        &lt;SidebarMenuButton asChild&gt;
          &lt;a href="https://docs.example.com" target="_blank" rel="noopener"&gt;
            &lt;BookOpen className="h-4 w-4" /&gt;
            &lt;span&gt;ドキュメント&lt;/span&gt;
            &lt;ExternalLink className="ml-auto h-3 w-3" /&gt;
          &lt;/a&gt;
        &lt;/SidebarMenuButton&gt;
      &lt;/SidebarMenuItem&gt;
      &lt;SidebarMenuItem&gt;
        &lt;SidebarMenuButton asChild&gt;
          &lt;a href="mailto:support@example.com"&gt;
            &lt;HelpCircle className="h-4 w-4" /&gt;
            &lt;span&gt;お問い合わせ&lt;/span&gt;
          &lt;/a&gt;
        &lt;/SidebarMenuButton&gt;
      &lt;/SidebarMenuItem&gt;
    &lt;/SidebarMenu&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

4.3 検索ボックスとクイックアクション

多くの管理画面では、サイドバーに検索ボックスやグローバル検索(Cmd+K)を配置します。shadcn/uiにはCommandコンポーネントがあります:

import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command";

&lt;SidebarGroup&gt;
  &lt;SidebarGroupContent&gt;
    &lt;Command className="rounded-lg border shadow-md"&gt;
      &lt;CommandInput placeholder="メニューを検索..." /&gt;
      &lt;CommandList&gt;
        &lt;CommandEmpty&gt;結果が見つかりません&lt;/CommandEmpty&gt;
        &lt;CommandGroup heading="おすすめ"&gt;
          {navConfig.map((item) =&gt; (
            &lt;CommandItem key={item.href} onSelect={() =&gt; router.push(item.href)}&gt;
              &lt;item.icon className="mr-2 h-4 w-4" /&gt;
              {item.title}
            &lt;/CommandItem&gt;
          ))}
        &lt;/CommandGroup&gt;
      &lt;/CommandList&gt;
    &lt;/Command&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

5. パフォーマンス最適化&ベストプラクティス

最後に、実践的な最適化のヒントをいくつか紹介します。

Server Componentsを優先

Next.js App Routerでは、デフォルトですべてのコンポーネントがServer Componentです。Sidebarの静的部分(Logo、固定メニュー項目など)はServer Componentのままで十分です——インタラクティブな部分(ルートハイライト、折りたたみ状態)だけが"use client"を必要とします。

私のアプローチ:

  • AppSidebar"use client"としてマーク(usePathnameを使用するため)
  • SidebarHeaderSidebarFooterの静的部分を別のServer Componentに抽出
  • ナビゲーション設定をサーバー側で生成し、クライアントコンポーネントに渡す

これにより、クライアントJSバンドルサイズが削減されます。

大規模メニューの遅延読み込み

管理画面に数十のメニュー項目がある場合、遅延読み込みを検討できます。React.lazyまたはNext.jsのdynamicを使用します:

import dynamic from "next/dynamic";

const AdminMenu = dynamic(() =&gt; import("./admin-menu"), {
  loading: () =&gt; &lt;SidebarMenuSkeleton /&gt;,
});

ただし、正直なところ、ほとんどの管理画面にはそこまで多くのメニュー項目がないため、この最適化は稀にしか必要ありません。

アクセシビリティのポイント

shadcn/uiのSidebarはRadix UI上に構築されているため、アクセシビリティはほぼ組み込まれています。それでも、以下の点に注意してください:

  1. アイコン+テキスト:アイコンだけを使用しない——スクリーンリーダーのユーザーには見えません
  2. フォーカス表示:デフォルトのフォーカススタイルをオーバーライドしない
  3. キーボードナビゲーション:Tabキーと方向キーが正常に動作するか確認

Radix UIが大部分を処理してくれますが、コンポーネントをカスタマイズする場合は、キーボードナビゲーションをテストすることを忘れないでください。


6. よくある質問

Q1: Sidebarの状態がリフレッシュ後に消える?

SidebarProviderをDashboard LayoutではなくRoot Layoutに配置しているか確認してください。Dashboard Layoutに配置すると、ルート変更時に状態がリセットされます。

Q2: モバイルでSidebarを自動的に閉じるには?

shadcn/uiのSidebarはモバイルで自動的にSheetドロワーになります。メニュー項目がクリックされたときに手動で閉じる必要があります:

const { setOpenMobile } = useSidebar();

&lt;SidebarMenuButton
  onClick={() =&gt; setOpenMobile(false)}
&gt;

Q3: Sidebarの幅をカスタマイズするには?

CSS変数を使用します:

&lt;Sidebar
  style={{
    "--sidebar-width": "280px",
    "--sidebar-width-mobile": "100%",
  }}
&gt;

または、sidebar.tsxSIDEBAR_WIDTH定数を変更します。


まとめ

shadcn/ui SidebarとNext.js Layoutを組み合わせることで、管理画面の骨組み構築が本当に効率的になります。重要なポイントを振り返りましょう:

  1. コンポーネントシステム:SidebarProvider、Sidebar、SidebarContentなどの責任を理解する
  2. レイアウト統合:Route Groupsで異なるレイアウトを分離し、SidebarProviderをRoot Layoutに配置
  3. 設定駆動:ナビゲーションメニューを設定ファイルに保存し、コンポーネントがそれに基づいてレンダリング——メンテナンスが簡単
  4. ルートハイライトusePathname() + isActiveプロップ——シンプルで直接的
  5. RBAC:設定にrolesフィールドを追加し、レンダリング時にフィルタリング

このアーキテクチャは複数のプロジェクトで使用していますが、拡張性は良好です。新しいページを追加するには、navConfigに1行追加するだけで——コンポーネントが残りを処理します。

質問があれば、コメントでどうぞ。次回はshadcn/ui DataTableの実践について書く予定です——お楽しみに。

shadcn/ui Sidebar + Next.js Layout 管理画面骨組みの構築

ゼロからスケーラブルな管理画面レイアウトを構築。サイドバー、ルートハイライト、RBACを含む

⏱️ 目安時間: 45 分

  1. 1

    ステップ1: shadcn/uiをインストールしてSidebarコンポーネントを追加

    CLIコマンドを実行してプロジェクトを初期化し、コンポーネントを追加:

    ```bash
    npx shadcn@latest init
    npx shadcn@latest add sidebar
    ```

    インストール中にスタイル設定を尋ねられます——デフォルトで構いません。完了後、components/uiディレクトリにsidebar.tsxが生成されます。
  2. 2

    ステップ2: Root Layoutを設定

    app/layout.tsxでSidebarProviderをラップ:

    • SidebarProviderコンポーネントをインポート
    • bodyタグ内で{children}をラップ
    • lang="ja"属性を設定

    これにより、Sidebarの状態がグローバルに永続化されます。
  3. 3

    ステップ3: Dashboard Layoutを作成

    app/(dashboard)/layout.tsxで管理画面専用レイアウトを作成:

    • Route Groups構文(dashboard)を使用
    • AppSidebarとSidebarInsetをインポート
    • パンくずリスト付きの上部Headerを追加

    Route GroupsはURL構造に影響しません——/dashboardパスはルートに直接マッピングされます。
  4. 4

    ステップ4: ナビゲーション設定を定義

    lib/navigation.ts設定ファイルを作成:

    • NavItemインターフェースを定義(title、href、icon、badge)
    • navConfig配列をエクスポート
    • オプションでRBAC用のrolesフィールドを追加

    設定駆動のアプローチにより、メニューの追加は1箇所の変更だけで済みます。
  5. 5

    ステップ5: AppSidebarコンポーネントを実装

    components/app-sidebar.tsxを作成:

    • "use client"でクライアントコンポーネントとしてマーク
    • usePathnameで現在のルートを取得
    • navConfigをマップしてメニュー項目をレンダリング
    • ルートが一致したらisActiveプロパティでハイライト
  6. 6

    ステップ6: RBACを追加(オプション)

    ロールベースのフィルタリングを実装:

    • NavItemインターフェースにrolesフィールドを追加
    • AppSidebarでuseAuth経由でユーザーロールを取得
    • メニュー項目にfilterメソッドを使用

    rolesフィールドがないメニューはデフォルトですべてのユーザーに表示されます。

FAQ

shadcn/ui SidebarとAnt Designのサイドバーの違いは?
shadcn/uiはCopy-pasteモデルを採用し、コンポーネントコードがプロジェクト内に存在——完全にコントロール可能です。Ant Designは完全なデザインシステムで、すぐに使えますがカスタマイズのコストが高いです。高いカスタマイズが必要な場合はshadcn/uiを、迅速な開発が必要な場合はAnt Designを選択してください。
SidebarProviderはRoot LayoutとDashboard Layoutのどちらに配置すべき?
Root Layout(app/layout.tsx)に配置することをお勧めします。これにより、Sidebarの折りたたみ状態がページ間で永続化されます。Dashboard Layoutに配置すると、ルート変更のたびに状態がリセットされます。
モバイルでSidebarを自動的に閉じるにはどうすればいい?
モバイルSidebarは自動的にSheetドロワーになります。メニュー項目がクリックされたときに以下を呼び出す必要があります:

```tsx
const { setOpenMobile } = useSidebar();
&lt;SidebarMenuButton onClick={() =&gt; setOpenMobile(false)}&gt;
```

これにより、メニュー項目クリック後にドロワーが自動的に閉じます。
Sidebarの幅をカスタマイズするには?
2つのアプローチがあります:

1. CSS変数を使用(推奨):
```tsx
&lt;Sidebar style={{ "--sidebar-width": "280px" }} /&gt;
```

2. sidebar.tsxのSIDEBAR_WIDTH定数を変更

CSS変数の方が柔軟で、異なるSidebarに異なる幅を設定できます。
マルチレベルメニューを実装するには?
CollapsibleコンポーネントでSidebarMenuItemをラップ:

• CollapsibleTriggerに第1レベルのメニューボタンを含める
• CollapsibleContentにSidebarMenuSubと第2レベルの項目を含める
• ChevronDownアイコンで展開可能を示す

完全なコードはセクション3.5を参照してください。
shadcn/ui Sidebarのアクセシビリティは?
Radix UI上に構築されており、完全なアクセシビリティサポートを標準装備:キーボードナビゲーション(Tab/方向キー)、ARIA属性、フォーカス管理。アイコンとテキストを両方表示するようにしてください——アイコンだけは使用しないでください。

参考資料

5 min read · 公開日: 2026年3月27日 · 更新日: 2026年3月27日

コメント

GitHubアカウントでログインしてコメントできます

関連記事