まくろぐ
更新: / 作成:

クエリ文字列とは

https://example.com/todos?sortby=title&order=asc

URL のクエリ文字列(クエリパラメーター)というのは、上記のような URL の末尾の ? 以降の、sortby=title&order=asc の部分のことを指します。 この部分を参照する方法としては、主に次の 2 種類の方法があります。

  • クライアントサイド JS … useRouter フックを使う
  • サーバーサイド JS … getServerSideProps に渡されるパラメーターを使う

クライアントサイド JS からクエリパラメーターを参照する (router.query)

Next.js の useRouter フック を使うと、上記のようなクエリパラメーター部分を簡単に抽出することができます。 次の例では、クエリパラメーターとして渡された sortbyorder の値を取得しています。 値が省略された場合は、それぞれの値は undefined になります。

src/pages/todos.tsx
import { NextPage } from 'next'
import { useRouter } from 'next/router'

const TodosPage: NextPage = () => {
  const router = useRouter()
  const { sortby, order } = router.query

  console.log(sortby)  // 指定されていなければ undefined
  console.log(order)   // 指定されていなければ undefined

  return (
    <p>{ sortby }  { order === 'asc' ? '昇順' : '降順' } ソートします</p>
  )
}

export default TodosPage
☝️ プリレンダリングと hydrate 処理の考慮

Next.js にはページコンポーネントのプリレンダリングの仕組み(あらかじめ JS 部分まで実行して静的な HTML を生成しておく仕組み)があり、その時点では URL のクエリ文字列は参照できないので、router.query オブジェクトも空っぽになっています。 上記の例で言えば、プリレンダリング時の sortbyorder の値は undefined になっているということです。 もちろん、実際にクエリパラメーター付きの URL で Web サイトにアクセスするタイミングでは、クライアントサイドで実行される JavaScript で router.query の値を参照できるようになっていますが、ページコンポーネントの実装では、クエリ文字列が指定されていない場合のことを考えておくべきです。

このように、Next.js アプリにおいて、ページコンポーネントに記述した JS コードが、プリレンダリング時とアクセス時に行われる仕組みは hydration (hydrate) と呼ばれています

サーバーサイド JS からクエリパラメーターを参照する (context.query)

Web サイトへのアクセス時に必ずサーバーサイドで呼び出される getServerSideProps 関数では、関数のパラメーターとしてクエリパラメーターを受け取ることができます。

serverSideProps 内でのクエリパラメーターの参照
import { GetServerSideProps, NextPage } from 'next'

/** ページの描画に使用するデータ(NextPage コンポーネントへの入力) */
type PageProps = {
  todos: Todo[]
}

/** サーバーサイドでのデータ取得処理 */
export const getServerSideProps: GetServerSideProps<PageProps> =
  async (context) => {
    const { sortby, order } = context.query  // URL の ? 以降のクエリパラメーター

    console.log(sortby)  // 指定されていなければ undefined
    console.log(order)   // 指定されていなければ undefined

    // 指定されたクエリパラメーターに基づいてデータ取得
    const props: PageProps = {
      todos: db.getTodos(sortBy, order)
    }

    return { props }
  }

/** ページコンポーネント */
const TodoPage: NextPage<PageProps> = ({ todos }) => {
  // ...
}

export default TodoPage

ちなみに、ビルド時に呼び出される getStaticProps 関数の中では、クエリパラメーターを取得することはできません。 クエリパラメーターは、あくまで実際にユーザーが Web サイトにアクセスするときに指定されるものという扱いであり、ビルド時にはクエリパラメーターまで含んだページを出力できないからです。 もっと具体的にいうと、001?sortby=title&order=asc.html のようなクエリまで含んだファイルを事前生成することはできないということです。

クエリ文字列を変更する

コンポーネントの中から URL のクエリ文字列部分を変更するには、NextRouter オブジェクトの replace メソッドの引数に渡すオブジェクトの query プロパティを指定します。 元の URL をブラウザの履歴に積みたいときは、replace の代わりに push メソッドを使用します。

// import NextRouter from 'next/router'

const updateQuery = (value: string) => {
  void NextRouter.replace({
    // 既存のクエリパラメーターとマージする形で keyName の値を書き換える
    query: { ...NextRouter.query, keyName: value },
  })
}

上記の例では、useRouter フックの代わりに、next/router モジュールがデフォルト export するオブジェクトを参照しています。 ユーザー操作によるトリガーでしか呼ばれない関数など、コンポーネントのライフサイクルから切り離された場所であれば、このデフォルトオブジェクトを使用できます。

複数のクエリパラメーターを一度に変更したい場合は、次のような updateQuery 関数を作っておくと便利かもしれません。 この関数は、複数のキー&バリューを受け取ることができます。

// import { ParsedUrlQueryInput } from 'querystring'
// import NextRouter from 'next/router'

/**
 * URL のクエリパラメーター部分を更新します。
 *
 * 使用例: updateQuery({ key1: "value1", key2: "value2" })
 */
function updateQuery(query: { [key: string]: string | number }) {
  // 既存のクエリパラメーターとマージ
  const newQuery: ParsedUrlQueryInput = { ...NextRouter.query, ...query }
  void NextRouter.replace({ query: newQuery })
}

関連記事

まくろぐ
サイトマップまくへのメッセージ