ルーティング

Qwik City におけるルーティングは、Next.jsSvelteKitSolidStart、またはRemixのように、ファイルシステムベースです。 src/routes 内のファイルとディレクトリは、アプリケーションのルーティングにおいて役割を果たします。

  • 📂 ディレクトリ:ルーターが一致させる URL セグメントを定義します。
  • 📄 index.tsx/mdx ファイル:ページを定義します。
  • 📄 index.ts ファイル:エンドポイントを定義します。
  • 🖼️ layout.tsx ファイル:ネストされたレイアウトおよび/またはミドルウェアを定義します。

ディレクトリベースのルーティング

ディレクトリ名のみが、ページ/エンドポイント/ミドルウェアへの着信リクエストのマッチングに使用されます。

たとえば、src/routes/some/path/index.tsx にファイルがある場合、URL パス https://example.com/some/path にマップされます。

ディレクトリのレイアウト
src/
└── routes/
    ├── contact/
       └── index.mdx         # https://example.com/contact
    ├── about/
       └── index.md          # https://example.com/about
    ├── docs/
       └── [id]/
           └── index.ts      # https://example.com/docs/1234
                             # https://example.com/docs/anything
    ├── [...catchall]/
       └── index.tsx         # https://example.com/anything/else/that/didnt/match
    
    └── layout.tsx            # This layout is used for all pages
  • [id]は、動的ルートセグメントを表すディレクトリです。この例では、iduseLocation().params.id でアクセスできる文字列パラメータです。
  • [...catchall]は、動的なキャッチオールルートを表すディレクトリです。この例では、catchalluseLocation().params.catchall でアクセスできる文字列パラメータです。
  • index.tsx|mdx ファイルは、ページ/エンドポイント/ミドルウェアです。
  • layout.tsx ファイルは、レイアウトです。

動的なルートセグメント

[paramName][...catchAll] など、角括弧で囲まれた特別な名前付きディレクトリを使用すると、動的なルートセグメントをマッチングできます。

ディレクトリのレイアウト
src/routes/blog/index.tsx  /blog
src/routes/user/[username]/index.tsx  /user/:username (/user/foo)
src/routes/post/[...all]/index.tsx  /post/* (/post/2020/id/title)
ディレクトリのレイアウト
src/
└── routes/
    ├── blog/
       └── index.tsx         # https://example.com/blog
    ├── post/
       └── [...all]/
           └── index.tsx     # https://example.com/post/2020/id/title
    └── user/
        └── [username]/
            └── index.tsx     # https://example.com/user/foo

[username] フォルダは、データベースにある何千ものユーザーのいずれでもかまいません。ユーザーごとにルートを作成するのは非現実的です。代わりに、[username] を抽出するために使用されるルートパラメータ(URLの一部)を定義する必要があります。

src/routes/user/[username]/index.tsx
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
 
export default component$(() => {
  const loc = useLocation();
  return <div>Hello {loc.params.username}!</div>;
});

index ファイル

src/routes ディレクトリ内では、index という名前のすべてのファイルがページ/エンドポイント/ミドルウェアと見なされます。Qwik は次の拡張子をサポートしています:.ts.tsx.md、および .mdx

ページ/エンドポイント/ミドルウェアは、ルーティングツリーのリーフノード、つまり、リクエストを処理して HTTP 応答を返すモジュールです。

ページ index.tsx

index.tsx または index.ts ファイルが Qwik コンポーネントをデフォルトのエクスポートとしてエクスポートすると、Qwik City はそのコンポーネントをレンダリングし、HTML 応答を Web ページとして返します。

src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
 
export default component$(() => {
  return <h1>Hello World</h1>;
});

エンドポイント index.ts

index.ts ファイルは、HTTP リクエストに直接アクセスし、Qwik コンポーネントを一切関与させずに生の HTTP 応答を返すことができます。これは、特定の HTTP リクエストの処理方法に応じて、onRequestonGetonPostonPut、または onDelete のいずれかのメソッドをエクスポートすることで行われます。

src/routes/index.ts
import type { RequestHandler } from '@builder.io/qwik-city';
 
export const onGet: RequestHandler = ({ json }) => {
  json(200, { message: 'Hello World' });
};

最後の例では、デフォルトのエクスポートがないことに注意してください。これは、Qwik コンポーネントをレンダリングしているのではなく、リクエストを直接処理して JSON 応答を返しているためです。これは、RESTful API やその他のタイプの HTTP エンドポイントを実装する場合に役立ちます。

ページ + エンドポイント

Qwik City では、ページとエンドポイントの間に明確な区別はありません。 index.tsx ファイルは、Qwik コンポーネントまたは onRequest メソッドをエクスポートすることで、両方を処理します。ただし、両方のアプローチを組み合わせることができます。たとえば、リクエストを処理する onRequest メソッドをエクスポートし、次に Qwik コンポーネントをレンダリングできます。

src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
import type { RequestHandler } from '@builder.io/qwik-city';
 
export const onRequest: RequestHandler = ({ headers, query, json }) => {
  headers.set('Cache-Control', 'private');
  if (query.get('format') === 'json') {
    json(200, { message: 'Hello World' });
  }
};
 
export default component$(() => {
  return <h1>Hello World</h1>;
});

この例では、リクエストハンドラは常にCache-Controlヘッダーをprivateに設定し、ページはHTMLページとしてレンダリングされます。ただし、リクエストにformat=jsonクエリパラメーターが含まれている場合、エンドポイントは代わりにJSONレスポンスを返します。

layout.tsxファイル

レイアウトモジュールはindexファイルと非常によく似ています。どちらもリクエストを処理し、Qwikコンポーネントをレンダリングできます。ただし、レイアウトはミドルウェアのように機能するように設計されており、一連のルートに対してUIとリクエスト処理(ミドルウェア)を共有できます。

通常、異なるページは共通のリクエスト処理を必要とし、一部のUIを共有します。たとえば、すべてのページが/admin/*ディレクトリの下にあるダッシュボードサイトを想像してください。

  • 共有リクエスト処理:ページをレンダリングする前に、リクエストのCookieを検証する必要があります。そうしないと、空白の401ページをレンダリングします。
  • 共有UI:すべてのページで、ユーザーの名前とプロフィール写真を表示する共通のヘッダーを共有します。

各ルートで同じコードを繰り返す代わりに、レイアウトを使用して共通部分を自動的に再利用します。レイアウトは、ルートにミドルウェアを追加することもサポートしています。

このsrc/routesディレクトリを例として取り上げます。

ディレクトリのレイアウト
src/
└── routes/
    ├── admin/
       ├── layout.tsx  <-- This layout is used for all pages under /admin/*
       └── index.tsx
    ├── layout.tsx      <-- This layout is used for all pages
    └── index.tsx

ミドルウェアレイアウト

レイアウトは、onRequestonGetonPostonPut、またはonDeleteのいずれかのメソッドを使用して、リクエスト処理を実装できます。これは、ミドルウェアを実装するために使用できることを意味します。たとえば、ページをレンダリングする前にリクエストのCookieを検証するために使用できます。

ルートhttps://example.com/adminの場合、onRequestメソッドは次の順序で実行されます。

  1. src/routes/layout.tsxonRequest
  2. src/routes/admin/layout.tsxonRequest
  3. src/routes/admin/index.tsxonRequest
  4. src/routes/admin/index.tsxのコンポーネント

src/routes/index.tsxonRequestハンドラーは実行されません。

ネストされたレイアウト

レイアウトは、レンダリングされたページに共通のUIを追加する方法も提供します。たとえば、すべてのルートに共通のヘッダーを追加する場合は、ルートレイアウトにHeaderコンポーネントを追加します。

与えられた例では、Qwikコンポーネントは次の順序でレンダリングされます。

  1. src/routes/layout.tsxのコンポーネント
  2. src/routes/admin/layout.tsxのコンポーネント
  3. src/routes/admin/index.tsxのコンポーネント
<RootLayout>
  <AdminLayout>
    <AdminPage />
  </AdminLayout>
</RootLayout>

SPAナビゲーション

Qwikを使用すると、MPAとSPAの区別がなくなります。すべてのアプリは同時に両方になることができます。この選択は、プロジェクトの開始時に決定されるアーキテクチャ設計ではなく、代わりに、すべてのリンクに対してこの決定を行うことができます。

Qwikは、<Link>コンポーネントとuseNavigate()フックを提供します。これらは、ページ間のSPAの更新またはナビゲーションを開始するために使用できます。

Linkコンポーネントは、HTMLの<a>タグを使用するため、ページ間を移動する最もアクセスしやすい方法であるため、ナビゲートするための推奨される方法です。ただし、プログラムでナビゲートする必要がある場合は、useNavigate()フックを使用できます。

import { component$ } from '@builder.io/qwik';
import { Link, useNavigate } from '@builder.io/qwik-city';
 
export default component$(() => {
  const nav = useNavigate();
  return (
    <div>
      <Link href="/about">About (preferred)</Link>
      <button onClick$={() => nav('/about')}>About</button>
    </div>
  );
});

Linkコンポーネントは、内部でuseNavigate()フックを使用します。

reloadプロパティを持つLinkコンポーネントは、現在のページを更新するために一緒に使用できます。引数なしでuseNavigate()フックからnav()関数を呼び出すこともできます。

import { component$ } from '@builder.io/qwik';
import { Link, routeLoader$, useNavigate } from '@builder.io/qwik-city';
 
export const useServerTime = routeLoader$(() => {
  // This will re-execute in the server when the page refreshes.
  return Date.now();
});
 
export default component$(() => {
  const nav = useNavigate();
  const serverTime = useServerTime();
 
  return (
    <div>
      <Link reload>Refresh (better accessibility)</Link>
      <button onClick$={() => nav()}>Refresh</button>
      <p>Server time: {serverTime.value}</p>
    </div>
  );
});

ページが更新されると、一致するすべてのrouteLoader$とサーバーハンドラー(onRequest)がサーバーで再実行され、UIがそれに応じて再レンダリングされます。

ページを更新している間、useLocation()からのisNavigatingブール値は、ページが完全にレンダリングされるまでtrueになります。

デフォルトでは、Linkコンポーネントは、ユーザーがUI内の対応するリンクにマウスオーバーするとすぐに、次のページのプリフェッチを開始します。したがって、ユーザーがリンクをクリックしたときにアプリケーションがプリフェッチを完了している場合、次のページはすぐに表示されます。QwikアプリケーションはすでにJavaScriptの遅延読み込みに優れていますが、この動作はコンテンツの多いページや、データベースまたはAPI呼び出しを待つ必要があるSSRページに役立ちます。

これが目的の動作でない場合は、prefetchプロパティをfalseに設定できます。

 <Link prefetch={false} href="/about">About</Link>

スクロール復元

Qwikは、ネイティブブラウザーエクスペリエンスを厳密に模倣した、SPA向けのクラス最高のスクロール復元を提供します。ユーザーは、SPAのすべての追加メリットを除いて、MPAからネイティブに期待されるものとまったく同じエクスペリエンスを受け取るはずです。

上記のいずれかの方法を使用してナビゲートすると、ユーザーは自動的にSPAにアップグレードされます。これは、現在のページと、ユーザーが移動してきたページに、関連付けられたSPAコンテキストがあることを意味します。

その後、ユーザーが通常の<a>タグをクリックすると、通常のナビゲーションが実行されます。この新しいページにはSPAコンテキストがなく、事実上MPAにダウングレードされます。必要に応じてこれらを切り替えることができ、ユーザーのエクスペリエンスはすべて同じであるかのように、MPAとSPAの間でシームレスに切り替わります。

ユーザーがリフレッシュ、戻る/進むボタン、ブラウザセッションの再起動などでSPA対応の履歴エントリを再訪問すると、Qwikはスクロール位置を自動的に復元し、必要に応じてSPAコンテキストに自己ブートストラップします。

この堅牢なエクスペリエンスを提供するために必要なスクリプトは、履歴エントリにSPAコンテキストがない限り、ロードされることも、ユーザーのブラウザーに送信されることもありません。純粋なMPAページは、このスクリプトをロードしません。これがQwikの魔法です。

Qwikのスクロール復元は、常にレンダリングと同期して発生します。Qwikの再開可能でファーストクラスのSSR/MPAの性質と組み合わせることで、ユーザーはスクロールのちらつきを経験することは決してありません。

Qwikのスクロール復元は、完全にhistoryベースです。これは、sessionStorageのようなものに依存する他の多くのフレームワークとは異なります。

Qwikがスクロール位置を記憶して復元する能力は非常に堅牢で、ブラウザセッションの再起動から、ブラウザデータをクリアするユーザーまで、すべてを乗り切ることができます。これは、他の多くのフレームワークでは当てはまりません。

SPA中にpushState()replaceState()を使用する際の注意点

SPAコンテキストを持つページでは、QwikはグローバルhistorypushState()関数とreplaceState()関数をパッチ適用します。これは、開発者として追加するカスタム状態も、SPAコンテキストを確実に受信できるようにするためです。

これらはパッチ適用されていますが、pushまたはreplaceする状態は、常に実際のObject型である必要があります。これは、QwikがSPAコンテキストをプロパティとして状態に自動的に追加できるようにする必要があるためです。

オブジェクトではない値を指定すると、Qwikは状態の新しいオブジェクトを作成し、指定された値を新しいキー{ _data: <your_value> }に追加します。

Qwikは、これが発生した場合、devモードでブラウザのコンソールに警告も表示します。

リクエストイベント

onRequestonGetonPostなどの各リクエストハンドラーには、ハンドラーの最初の引数としてRequestEventオブジェクトが渡されます。RequestEventオブジェクトには、サーバーのリクエストとレスポンスの値を取得および設定するためのユーティリティ関数とプロパティが含まれています。このオブジェクトには、次のプロパティが含まれています。

  • basePathname:リクエストのベースパス名。ビルド時に構成できます。デフォルトは/です。
  • cacheControlCache-Controlレスポンスヘッダーを設定するための便利な関数。
  • cookie:HTTPリクエストとレスポンスのクッキー。リクエストのCookie値を取得するには、get()メソッドを使用します。レスポンスのCookie値を設定するには、set()メソッドを使用します。
  • env:プラットフォームが提供する環境変数。
  • error:呼び出されると、レスポンスは指定されたステータスコードですぐに終了します。これは、404でレスポンスを終了し、routesディレクトリで404ハンドラーを使用するのに役立ちます。使用する必要があるステータスコードについては、ステータスコードを参照してください。
  • getWritableStream:HTTPレスポンスストリームに書き込むための低レベルアクセス。getWritableStream()が呼び出されると、ステータスとヘッダーは変更できなくなり、ネットワーク経由で送信されます。
  • headers:HTTPレスポンスヘッダー
  • html:HTMLボディレスポンスを送信するための便利なメソッド。レスポンスには、自動的にContent-Typeヘッダーがtext/html; charset=utf-8に設定されます。html()レスポンスは一度しか呼び出すことができません。
  • json:データをJSON文字列化してレスポンスで送信するための便利なメソッド。レスポンスには、自動的にContent-Typeヘッダーがapplication/json; charset=utf-8に設定されます。json()レスポンスは一度しか呼び出すことができません。
  • locale:コンテンツがどのロケールにあるか。ロケール値は、getLocale()を使用して選択されたメソッドから取得できます。
  • method:HTTPリクエストのメソッド値。
  • next:次のリクエストハンドラーを呼び出します。これはミドルウェアに役立ちます。
  • params:現在のURLパス名セグメントから解析されたURLパスパラメーター。代わりにクエリ文字列の検索パラメーターを取得するには、queryを使用します。
  • parseBody:このメソッドは、リクエストヘッダーでContent-Typeヘッダーをチェックし、それに応じて本文を解析します。application/jsonapplication/x-www-form-urlencoded、およびmultipart/form-dataのコンテンツタイプをサポートしています。Content-Typeヘッダーが設定されていない場合は、nullを返します。
  • pathname:URLパス名の値。プロトコル、ドメイン、クエリ文字列(検索パラメーター)、またはハッシュは含まれません。
  • platform:プラットフォーム固有のデータと関数。
  • query:URLクエリ文字列のURLSearchParamsの値。代わりにURLパス名にあるルートパラメーターを取得するには、paramsを使用します。
  • redirect:リダイレクト先のURL。呼び出されると、レスポンスは正しいリダイレクトステータスとヘッダーですぐに終了します。使用する必要があるステータスコードについては、リダイレクトを参照してください。
  • request:HTTPリクエスト
  • send:本文レスポンスを送信します。send()を使用すると、Content-Typeレスポンスヘッダーは自動的に設定されないため、手動で設定する必要があります。send()レスポンスは一度しか呼び出すことができません。
  • sharedMap:すべてのリクエストハンドラーで共有されるマップ。すべてのHTTPリクエストは、共有マップの新しいインスタンスを取得します。共有マップは、リクエストハンドラー間でデータを共有するのに役立ちます。
  • status: HTTPレスポンスのステータスコード。引数付きで呼び出すとステータスコードを設定します。常にステータスコードを返すため、引数なしでstatus()を呼び出すと現在のステータスコードを返すことができます。
  • text: テキストボディのレスポンスを送信するための便利なメソッド。レスポンスには自動的にContent-Typeヘッダーがtext/plain; charset=utf-8に設定されます。text()レスポンスは一度しか呼び出すことができません。
  • url: HTTPリクエストのURL

ルートのリライト

複数のページで、単一のページコンポーネントと、その独自のミドルウェアおよびレイアウトを再利用するために、パス名を書き換えることができます。これは、SEO目的や、ページを異なる言語に翻訳するのに役立ちます。

プレフィックス付きのローカライズされたURLの翻訳

ローカライズの目的で、/products から /it/prodotti/fr/produits へ、または /products/product-name から /it/prodotti/nome-prodotto/fr/produits/nom-du-produit へとルートを翻訳したい場合があります。それぞれのロケールごとに複数のルートファイルを用意するのではなく、同じページコンポーネント、レイアウト、ミドルウェアなどを再利用できます。

パラメータ名は変更されないため、ルートファイルが /products/[slug]/index.tsx で、URLが /products/product-name/it/prodotti/nome-prodotto、または /fr/produits/nom-du-produit の場合、同じパスパラメータ slugproduct-namenome-prodotto、または nom-du-produit の値で返されます。

プレフィックスなしのURLのリライト

まれですが、同じパスに対してエイリアスを設定したい場合があります。たとえば、/docs/documents の両方を同じページコンポーネントからレンダリングしたい場合や、/products/it プレフィックスを追加せずに /prodotti に翻訳したい場合があります。

pathname内のパスキーのインスタンスが存在するルートディレクトリ内の各フォルダーで、ルートノードが複製され、パスキーのすべての出現箇所が対応するパス値に置き換えられます。すべてのパスパラメータは同じ名前を保持します。プレフィックスがある場合は、書き換えられたpathnameの先頭に追加されます。

vite.config.tsで次のようにリライトルールを設定できます。

import { defineConfig } from 'vite';
import { qwikCity } from '@builder.io/qwik-city/vite';
 
export default defineConfig(async () => {
  return {
    plugins: [
      qwikCity({
        rewriteRoutes: [
            {
              paths: {
                  'docs': 'documentation'
              },
            },
            {
              prefix: 'it',
              paths: {
                'docs': 'documentazione',
                'getting-started': 'per-iniziare',
                'products': 'prodotti',
              },
            },
          ],
      }),
    ],
  };
});

高度なルーティング

Qwik Cityは以下もサポートしています

タイプセーフルーティング

これらについては後で説明します。

貢献者

このドキュメントの改善にご協力いただいたすべての貢献者に感謝します!

  • manucorporat
  • nnelgxorz
  • the-r3aper7
  • Oyemade
  • mhevery
  • adamdbradley
  • wtlin1228
  • AnthonyPAlicea
  • hamatoyogi
  • jakovljevic-mladen
  • claudioshiver
  • maiieul
  • igorbabko
  • jordanw66
  • mrhoodz
  • chsanch
  • RumNCodeDev