1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.jsでDev.toライクな3カラムレイアウトを実装する

Posted at

ブログサイトのレイアウトを改善する際、Dev.toのような3カラム構成は非常に効果的な選択肢の一つです。この記事では、Next.jsとTailwind CSSを用いてDev.toライクな3カラムレイアウトを実装する方法について、実践的な知見を共有します。

はじめに

技術ブログを運営していると、以下のような課題に直面することが多いのではないでしょうか:

  • 見出しや関連情報へのジャンプが分かりにくい
  • デスクトップ画面での余白が効果的に活用できていない
  • タグ一覧や執筆者情報などの追加情報を表示するスペースが不足している
  • レスポンシブ対応が不完全で、モバイル表示時に使いづらい

これらの課題を解決するため、Dev.toで採用されているような3カラムレイアウトの実装方法について解説します。

前提条件

この記事で説明する実装には、以下の技術スタックを使用します:

  • Next.js(App Router)
  • TypeScript
  • Tailwind CSS
  • React 18以上

Dev.toスタイルの3カラムレイアウトとは

Dev.toスタイルの3カラムレイアウトは、以下のような構成を特徴としています:

  • 左サイドバー(240px)
    • サイト全体のナビゲーション
    • シリーズ記事リンク
    • タグ一覧
    • 著者情報
  • メインコンテンツ(可変幅)
    • 記事本文
    • 広いスペースでの快適な読書体験
  • 右サイドバー(240px)
    • シェアボタン
    • 目次
    • 関連記事
    • モバイル時はスライドアウトメニュー化

この構成により、技術系ブログで多くの読者が慣れ親しんだUIを提供しながら、ナビゲーション性とスペース活用を向上させることができます。

実装のポイント

1. 基本的なレイアウト構造

まず、3カラムレイアウトの基本構造を実装します。Tailwind CSSのユーティリティクラスを活用することで、簡潔なコードでレスポンシブ対応を実現できます。

// components/layouts/BlogLayout.tsx
import { ReactNode } from 'react'
import LeftSidebar from './LeftSidebar'
import RightSidebar from './RightSidebar'

type Props = {
  children: ReactNode
}

export default function BlogLayout({ children }: Props) {
  return (
    <div className="relative min-h-screen lg:flex">
      {/* 左サイドバー */}
      <aside className="fixed inset-y-0 left-0 hidden w-60 overflow-y-auto border-r border-gray-200 lg:block">
        <LeftSidebar />
      </aside>

      {/* メインコンテンツ */}
      <main className="mx-auto w-full max-w-3xl px-4 py-8 lg:ml-60 xl:px-0">
        {children}
      </main>

      {/* 右サイドバー */}
      <aside className="fixed inset-y-0 right-0 hidden w-60 overflow-y-auto border-l border-gray-200 xl:block">
        <RightSidebar />
      </aside>
    </div>
  )
}

ここでのポイントは以下の通りです:

  • lg:flexを使用して、大画面時のみフレックスボックスレイアウトを適用
  • サイドバーはw-60(240px)で固定幅を設定
  • メインコンテンツはmax-w-3xlで最大幅を制限
  • 左サイドバーはlg:blockで、右サイドバーはxl:blockでブレークポイントを分けて表示

2. モバイル対応の実装

モバイル対応では、サイドバーをドロワーメニューとして実装します。これにより、限られた画面スペースを効率的に活用できます。

// components/layouts/MobileSidebar.tsx
import { useState } from 'react'

export default function MobileSidebar() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      {/* ハンバーガーメニューボタン */}
      <button
        onClick={() => setIsOpen(true)}
        className="fixed left-4 top-4 z-50 block lg:hidden"
        aria-label="メニューを開く"
        aria-expanded={isOpen}
        aria-controls="mobile-menu"
      >
        <svg className="h-6 w-6" /* ... */ />
      </button>

      {/* オーバーレイ */}
      {isOpen && (
        <div
          className="fixed inset-0 z-40 bg-black bg-opacity-50 lg:hidden"
          onClick={() => setIsOpen(false)}
        />
      )}

      {/* ドロワーメニュー */}
      <div
        id="mobile-menu"
        className={`fixed inset-y-0 left-0 z-50 w-60 transform overflow-y-auto bg-white transition duration-300 ease-in-out lg:hidden ${
          isOpen ? 'translate-x-0' : '-translate-x-full'
        }`}
      >
        <LeftSidebar />
      </div>
    </>
  )
}

実装のポイント:

  • useStateでドロワーの開閉状態を管理
  • transformtranslateでスライドアニメーションを実現
  • 背景のオーバーレイで操作を制限
  • アクセシビリティに配慮したARIA属性の設定

3. シェアボタンの実装

モバイルとデスクトップで異なるシェア機能を提供するため、Web Share APIを活用します。

// components/ShareButton.tsx
'use client'

import { useEffect, useState } from 'react'

export default function ShareButton({ url, title }: { url: string; title: string }) {
  const [canShare, setCanShare] = useState(false)

  useEffect(() => {
    setCanShare(!!navigator.share)
  }, [])

  const handleShare = async () => {
    try {
      if (navigator.share) {
        await navigator.share({
          title,
          url,
        })
      } else {
        // フォールバック:URLをクリップボードにコピー
        await navigator.clipboard.writeText(url)
        alert('URLをコピーしました')
      }
    } catch (error) {
      console.error('シェアに失敗しました:', error)
    }
  }

  return (
    <button
      onClick={handleShare}
      className="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
    >
      <svg className="mr-2 h-5 w-5" /* ... */ />
      {canShare ? 'シェア' : 'URLをコピー'}
    </button>
  )
}

実装のポイント:

  • use clientディレクティブでクライアントコンポーネントとして宣言
  • navigator.shareの存在チェックでWeb Share APIの対応を判定
  • フォールバックとしてクリップボードコピー機能を実装

実装時の注意点

1. ハイドレーションエラーへの対策

Next.jsでSSRを使用する場合、ブラウザ固有のAPIを直接参照するとハイドレーションエラーが発生する可能性があります。これを防ぐため:

  • クライアントサイド機能はuse clientディレクティブを使用して分離
  • useEffect内でブラウザAPIの参照を行う
  • SSR時とCSR時で表示が異なる部分は適切にローディング状態を表示

2. アクセシビリティへの配慮

3カラムレイアウトを実装する際は、以下のアクセシビリティ対応が重要です:

  • 適切なARIA属性の設定(aria-labelaria-expandedaria-controls
  • キーボード操作のサポート
  • スクリーンリーダー対応の階層構造
  • 十分なコントラスト比の確保

3. パフォーマンスの最適化

レイアウトのパフォーマンスを最適化するためのポイント:

  • サーバーコンポーネントとクライアントコンポーネントの適切な分離
  • 画像の最適化(next/imageの活用)
  • 不要なリレンダリングの防止
  • Tailwind CSSのパージによる最適化

コード公開

この記事で紹介した実装のサンプルコードは、以下のGitHubリポジトリで公開しています:

yonaka15/tech-jugoya-ai

実装時の参考にしていただければ幸いです。

参考資料

この記事は以下の記事を参考に、Qiita向けに再構成したものです:

まとめ

Dev.toスタイルの3カラムレイアウトは、技術ブログのUI/UX改善に非常に効果的です。主なメリットは:

  • ナビゲーション性の向上
  • 画面スペースの効率的な活用
  • モバイルフレンドリーな対応
  • 拡張性の高い設計

実装時は、レスポンシブデザイン、ハイドレーションエラー、アクセシビリティなど、考慮すべき要素が多くありますが、本記事で紹介した方法を参考に、段階的に改善を進めることをお勧めします。

また、このレイアウト構造は将来的な機能追加(シリーズ記事の目次連動やブックマーク機能など)にも対応しやすい設計となっています。

Next.jsとTailwind CSSを使用した実装例を通じて、読者の皆さんがより良い技術ブログの UI/UX を実現する一助となれば幸いです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?