Nextjs (App Router) と Bootstrap で固定レイアウトの Application Bar を作る
概要
タイトルの通りです。
具体的には /
のページが下の図のようになっていたとき、
/dashboard
とか他のページに遷移したときでも、画面の上にある Application Bar (NavBar) と下にある footer のレイアウトは固定されたままにするということです。
この記事では react-bootstrap
を使っていませんが、使う場合でも基本的な部分は多分同じです。
環境とかバージョンなど
JavaScript でやります。TypeScript でも手順自体はほぼ同じでいけます。
$ node -v
16.16.0
$ yarn -v
1.22.19
- bootstrap: 5.3.1
- create-next-app: 13.4.12
- next: 13.4.12
- react: 18.2.0
- react-dom: 18.2.0
準備
アプリケーションの作成
$ yarn create-next-app@latest
✔ What is your project named? … next-bootstrap-appbar
✔ Would you like to use TypeScript? … No
✔ Would you like to use ESLint? … No
✔ Would you like to use Tailwind CSS? … No
✔ Would you like to use `src/` directory? … No
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias? … No
$ cd next-bootstrap-appbar
$ yarn add bootstrap
動作テスト
$ yarn dev
ブラウザで https://localhost:3000
を開いてページが表示されたらとりあえずOKです。
改造
ファイル構造
dashboard
ディレクトリを作成して page.js
を作っておきます。この /app/dashboard/page.js
というファイルは、ブラウザ上では /dashboard
というパスでアクセスできます。
$ tree app
app/
├── dashboard # 追加
│ └── page.js # 追加
├── favicon.ico
├── globals.css
├── layout.js
└── page.js
/app/layout.js
の中でアプリケーションバー (NavBar) を作成しておき、<main>
要素の中に /app/page.js
の中身を展開する感じにします。`
ブラウザで /dashboard
に遷移したときも、/app/layout.js
と /app/dashboard/page.js
の両方を使って画面がレンダリングされるので、/app/layout.js
に作成してあるアプリケーションバーが表示されます。
各ファイルの中身
layout.js
HTML のコード自体は下記ページのほぼコピペです。
import 'bootstrap/dist/css/bootstrap.min.css';
import styles from './globals.css';
export default function RootLayout({children}){
return <>
<html lang="en" className='h-100'>
<body className="d-flex flex-column h-100">
<header>
<nav className="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div className="container-fluid">
<a className="navbar-brand" href="#">Fixed navbar</a>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarCollapse">
<ul className="navbar-nav me-auto mb-2 mb-md-0">
<li className="nav-item">
<a className="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li className="nav-item">
<a className="nav-link" href="#">Link</a>
</li>
<li className="nav-item">
<a className="nav-link disabled" href="#" tabIndex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form className="d-flex">
<input className="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
<button className="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</header>
<main className={styles.main}>
<div className="container">
{children}
</div>
</main>
<footer className="footer mt-auto py-3 bg-dark">
<div className="container">
<span className="text-light">Powered by BBLED 2023.</span>
</div>
</footer>
</body>
</html>
</>
}
global.css
padding
を設定して、<main>
の中身が NavBar の領域と重複することを防ぎます。これも、下記コードからほぼそのままコピペしたものです。
main {
padding: 60px 15px 0;
}
page.js
/dashboard
へ遷移するためのボタンを表示します。
/app/page.js
は /app/layout.js
の <main>
の中にある {children}
の部分に展開されるため、結果的に NavBar の下にボタンが表示されることになります。
useRouter()
はページ遷移のために使っています。useRouter()
を使うときは "use client"
の指定が必要になります。
'use client'
import { useRouter } from 'next/navigation'
export default function Home() {
const router = useRouter()
return <>
<div className="my-3">
<div type="button" className='btn btn-primary' onClick={() => router.push('/dashboard')}>
to Dashboard
</div>
</div>
</>
}
dashboard/page.js
/
へ遷移するためのボタンを表示します。基本、/app/page.js
と同じですが、このコードでは例示のために遷移部分を関数として定義しています。
"use client"
import { useRouter } from 'next/navigation'
export default function Dashboard() {
const router = useRouter();
const clickButton = (href) => {
router.push(href);
}
return <>
<div className="my-3">
<div type="button" className='btn btn-secondary' onClick={() => clickButton('/')}>
Top Page
</div>
</div>
</>
}
実行
$ yarn dev
ブラウザで https://localhost:3000
を開きます。
上の図のように、ボタンを押して画面が切り替われば成功です。
Discussion