基本的なルーティング
■ pages配下が対象
基本的にはNext.jsのプロジェクトを作成したときにデフォルトで配置されている、
pagesディレクトリの配下
を参照し、ルーティングされる。
サーバを起動し、ローカルホストのリンク(http://localhost:3000など) を入力しリクエストを送れば、通常以下のようなページにアクセスできる。
ここで、pages配下に一つフォルダを作成し、その中にindex.jsを作成する。(フォルダ名は任意)
【 ディレクトリ階層 】
root/
├ src/
│ └ pages/
│ └ sample/
│ └ index.js
【 index.js 】
export default function Home() {
return <h1>HOME</h1>;
}
URLのリンクを「 ローカルホスト / sample 」
(http://localhost:3000/sample) に変更し、リクエストを飛ばすと、以下のようなページが表示される。
index.js以外のファイルを作成すると
先ほどのように、フォルダ直下にindex.js
を作成した場合は、
「リンク / フォルダ名」
でページにアクセスすることができる。
しかし、index.js以外の名前のファイルを作成した場合は、
「リンク / フォルダ名 / ファイル名」
とすることで、ページにアクセスすることができる。
【 ディレクトリ階層 】
root/
├ src/
│ └ pages/
│ └ sample/
│ └ blog.js
【 blog.js 】
export default function Blog() {
return <h1>Blog</h1>;
}
リクエスト結果は以下の通り。
動的なルーティング(ダイナミックルーティング)
ダイナミックルーティングとは
URLの一部分を動的に変更する値にすることで、一つのファイルで複数のルーティングを可能にすること。
以下の例では、末尾の数字を動的に変更するダイナミックルーティングを作成する。
【 ディレクトリ階層 】
root/
├ src/
│ └ pages/
│ └ blog/
│ └ [number].js
ダイナミックルーティングを作成するときは、ファイル名を[]で囲い
、[任意の名前].js
とする必要がある。
こうすることで、[]内の名前(今回の例でいうnumber)
がダイナミックルーティングとなり、jsファイル内で値として扱えるようになる。
【 [number].js 】
export default function Number() {
return <h1>[number].js</h1>;
}
URLのリンクを「 ローカルホスト / blog / 1 」
にし、リクエストを飛ばす。
結果は以下の通り。
この時、末尾の1
は好きな値に置き換えても同様のページにアクセスできる。
ダイナミックルーティングの値をjs内で取得する方法
取得する方法は以下の2通り。
① getServerSidePropsという関数を使用する。
② React HookのuseRouterを使用する。
① getServerSidePropsを使用する
getserverSidePropsメソッドから戻されたオブジェクトのprops
というプロパティに設定された値が、関数コンポーネントのプロップスとして渡ってくるという仕組み。
関数コンポーネントにpropsを渡してみる
【 ディレクトリ階層 】
root/
├ src/
│ └ pages/
│ └ blog/
│ └ [number].js
【 [number].js 】
export default function Number({ hello }) {
return <h1>{hello}.js</h1>;
}
export async function getServerSideProps() {
return {
props: { hello: "こんにちは" },
};
}
リクエストを送信。結果は以下の通り。
動的にpropsの値を取得する
動的に値を取得するには、getserverSidePropsメソッドの引数である、context
を利用する。
【 [number].js 】
export default function Number({ hello }) {
return <h1>{hello}.js</h1>;
}
export async function getServerSideProps(context) {
console.log(context);
return {
props: { hello: "こんにちは" },
};
}
consoleの出力結果を確認すると大量のログが出てくるが、注目するべきなのは以下のログ。
{
query: { number: '1' },
resolvedUrl: '/07_router/blog/1',
params: { number: '1' },
locales: undefined,
locale: undefined,
defaultLocale: undefined
}
このquery
というところに、自分が入力した任意のパスの値が入っていることが分かる。
つまり、このcontext
内のquery.number
にアクセスすれば、リクエスト時に入力した任意の値を動的に取得することが可能になる。
早速実践。
export default function Number({ query }) {
return <h1>{query.number}.js</h1>;
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}
結果は以下の通り。
先ほどのログでは、
query: { number: '1' },
となっていたが、オブジェクトのプロパティ名はダイナミックルーティングの[]内に記載した名前によって変わる。
② React HookのuseRouterを使用する
先ほどのgetServerSidePropsとは違うメソッドになるが、利用の仕方はほぼ同じ。
【 [number.js] 】
import { useRouter } from "next/router";
export default function Number({ query }) {
const router = useRouter();
console.log(router);
return <h1>{router.query.number}.js</h1>;
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}
結果とログは以下の通り。
ログ
ServerRouter {
route: '/07_router/blog/[number]',
pathname: '/07_router/blog/[number]',
query: { number: '1' },
asPath: '/07_router/blog/1',
isFallback: false,
basePath: '',
locale: undefined,
locales: undefined,
defaultLocale: undefined,
isReady: true,
domainLocales: undefined,
isPreview: false,
isLocaleDomain: false
}
useRouterからクエリパラーメータを取得
【クエリパラメータ】
URLのリンクの?以降のkeyと値のセットのこと。
例)http://localhost:8080/sample/1?key=value
【 リクエストのリンク 】
【 ログ 】
useRouterを利用して画面遷移を行う
useRouterのpush
メソッドを利用することで、画面遷移を行うことが可能。
import { useRouter } from "next/router";
export default function Number({ query }) {
const router = useRouter();
console.log(router);
const clickHandler = () => {
// トップページに戻る
router.push("/");
};
return (
<>
<h1>{router.query.number}.js</h1>
<button onClick={clickHandler}>アクションによる画面遷移</button>
</>
);
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}
【 画面 】
【 ボタンクリック後 】
補足
push
メソッドは、第二引数にダミーのURLを設定することで、第一引数のリンクに遷移しつつ、画面に表示されるリンクを第二引数にすることができる。
import { useRouter } from "next/router";
export default function Number({ query }) {
const router = useRouter();
console.log(router);
const clickHandler = () => {
// トップページに戻る
router.push("/", "dummy-url");
};
return (
<>
<h1>{router.query.number}.js</h1>
<button onClick={clickHandler}>アクションによる画面遷移</button>
</>
);
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}
【 画面(遷移前) 】
【 遷移後 】
遷移 + 履歴を上書き
push
メソッドと同様に画面遷移をすることに加え、履歴を上書きするメソッドとして、replace
メソッドが存在する。
解説(pushメソッド)
まずpush
メソッドの場合の挙動確認。最初に以下のリンクにアクセス。
ここから「アクションによる画面遷移」ボタンをクリックし、トップページに遷移する。
このときにブラウザバック(左上の矢印の戻るボタン)ボタンをクリックすると、
先ほどの一番最初にリクエストで送信した画面に戻る。
という挙動になる。push
メソッドはこれまでの履歴に1つ履歴を追加する
というメソッドという認識が正しい。
googleトップページ → blog/1のページ → dummy-url(履歴を追加)
解説(replaceメソッド)
次にreplaceメソッドの挙動を確認。ソースは以下の通り。
import { useRouter } from "next/router";
export default function Number({ query }) {
const router = useRouter();
console.log(router);
const clickHandler = () => {
// トップページに戻る
router.push("/", "dummy-url");
};
return (
<>
<h1>{router.query.number}.js</h1>
<button onClick={clickHandler}>アクションによる画面遷移</button>
</>
);
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}
この状態で先ほどと同じ動作確認を実行。
ボタンを押下し、画面遷移。
そしてこの状態でブラウザバックボタンを押すと、リクエストを飛ばす前のgoogleのトップページへ遷移する。
これがreplace
メソッドの上書きである。
googleトップページ → blog/1のページ → dummy-url(履歴を上書き)
フォルダもダイナミックルーティングできる
【 ディレクトリ階層 】
root/
├ src/
│ └ pages/
│ └ [name]/
│ └ settings.js
│ └ blog/
│ └ [number].js
【 settings.js 】
export default function Settings() {
return <h1>[name]/settings.js</h1>;
}
URLのパスを、「 ローカルホスト / A / settings 」
にし、リクエストを飛ばす。
結果は以下の通り。
もちろん、 / A / のところはどんな名前でもアクセス可能。
ダイナミック vs 固定のパス
root/
├ src/
│ └ pages/
│ └ [name]/
│ └ settings.js
│ └ blog/
│ └ [number].js
上記のようなディレクトリ階層の時、
ローカルホスト / blog / settings
というリンクでリクエストを飛ばすとどうなるのかを検討する。
/blog/
はblogフォルダはもちろんのこと、[name]フォルダも該当する。
/settings/
も同様に、どちらのファイルにも該当する。
結果は以下の通り。
結果は固定パスが優先
ダイナミックルーティングを行なっている同階層に同じ固定のパスがあり、そちらに一致する場合は、固定パスの方が優先される
ということになる。
ダイナミックvsダイナミック
それでは、同階層にダイナミックルーティングが2つ以上あったらどうなるのか。
結論、これはエラー
になる。
システム上、同階層に設定できるダイナミックルーティングは1つまでというルールがあるので、同階層に複数のダイナミックルーティングの設定はできない。
Linkコンポーネントを利用した画面遷移
Linkコンポーネントを使用して、画面遷移を行う方法も紹介。
import { useRouter } from "next/router";
import Link from "next/link";
export default function Number({ query }) {
return (
<>
<Link href="../set/settings">
<a>[name]/settingsに遷移</a>
</Link>
</>
);
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}
【 画面 】
【 リンク押下 】
メリット
Linkコンポーネントを使用した遷移のメリットとしては、遷移するときに画面のリロードがされないので、画面遷移の高速化が期待できる。
試しにLinkコンポーネントと通常のaタグで遷移した場合の挙動確認を行う。
Linkコンポーネントの場合
背景を水色にし、Linkコンポーネント側のリンクで画面遷移をすると、
背景色が変わらないまま画面遷移ができる。
アンカータグの場合
同様の条件でaタグのリンクをクリックすると、
画面がリロードされるため、ディベロッパーツールで設定したスタイルが初期化され、背景色が白色に戻っていることがわかる。
アンカータグの画面遷移による弊害
アンカータグの繊維による弊害は、Linkコンポーネントと比較して低速というだけではない。
リロードを行うと、state(状態)で管理している値
が初期化されてしまう。
以下の例は、ある画面で保持した値を別画面に共有して表示する例である。
「リストページへ」ボタンを押下し、赤枠のデータを別画面に共有する。
値が保持されていることが確認できる。
しかし、これをアンカータグで遷移すると、
このように、リロードされたことによりstateの値が初期化されるので、遷移したときには何も表示されないという現象が起きる。
その他リンクの設定も可能
useRouterのpush
メソッドやreplace
メソッドと同様、画面表示用のダミーURLを設定することが可能。
Linkコンポーネントにas属性
を設定することで設定ができる。
import { useRouter } from "next/router";
import Link from "next/link";
export default function Number({ query }) {
const router = useRouter();
console.log(router);
const clickHandler = () => {
// トップページに戻る
router.replace("/", "dummy-url");
};
return (
<>
<Link href="/" as="dummy-url">
<a>トップ</a>
</Link>
</>
);
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}
クエリパラメータの設定や、リンクをオブジェクト形式で記載することも可能。
import { useRouter } from "next/router";
import Link from "next/link";
export default function Number({ query }) {
const router = useRouter();
console.log(router);
const clickHandler = () => {
// トップページに戻る
router.replace("/", "dummy-url");
};
return (
<>
<Link
href={{
// パスのオブジェクト設定
pathname: "/",
// クエリパラメータの設定
query: { key: "value" },
}}
as="dummy-url"
>
<a>トップ</a>
</Link>
</>
);
}
export async function getServerSideProps({ query }) {
return {
props: { query },
};
}