ルーティング
Qwik City におけるルーティングは、Next.js、SvelteKit、SolidStart、または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]
は、動的ルートセグメントを表すディレクトリです。この例では、id
はuseLocation().params.id
でアクセスできる文字列パラメータです。[...catchall]
は、動的なキャッチオールルートを表すディレクトリです。この例では、catchall
はuseLocation().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の一部)を定義する必要があります。
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 ページとして返します。
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <h1>Hello World</h1>;
});
index.ts
エンドポイント index.ts
ファイルは、HTTP リクエストに直接アクセスし、Qwik コンポーネントを一切関与させずに生の HTTP 応答を返すことができます。これは、特定の HTTP リクエストの処理方法に応じて、onRequest
、onGet
、onPost
、onPut
、または onDelete
のいずれかのメソッドをエクスポートすることで行われます。
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 コンポーネントをレンダリングできます。
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
ミドルウェアレイアウト
レイアウトは、onRequest
、onGet
、onPost
、onPut
、またはonDelete
のいずれかのメソッドを使用して、リクエスト処理を実装できます。これは、ミドルウェアを実装するために使用できることを意味します。たとえば、ページをレンダリングする前にリクエストのCookieを検証するために使用できます。
ルートhttps://example.com/admin
の場合、onRequest
メソッドは次の順序で実行されます。
src/routes/layout.tsx
のonRequest
src/routes/admin/layout.tsx
のonRequest
src/routes/admin/index.tsx
のonRequest
src/routes/admin/index.tsx
のコンポーネント
src/routes/index.tsx
のonRequest
ハンドラーは実行されません。
ネストされたレイアウト
レイアウトは、レンダリングされたページに共通のUIを追加する方法も提供します。たとえば、すべてのルートに共通のヘッダーを追加する場合は、ルートレイアウトにHeaderコンポーネントを追加します。
与えられた例では、Qwikコンポーネントは次の順序でレンダリングされます。
src/routes/layout.tsx
のコンポーネントsrc/routes/admin/layout.tsx
のコンポーネント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()
フックを使用します。
<Link reload>
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 prefetch>
デフォルトでは、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はグローバル
history
のpushState()
関数とreplaceState()
関数をパッチ適用します。これは、開発者として追加するカスタム状態も、SPAコンテキストを確実に受信できるようにするためです。これらはパッチ適用されていますが、
push
またはreplace
する状態は、常に実際のObject
型である必要があります。これは、QwikがSPAコンテキストをプロパティとして状態に自動的に追加できるようにする必要があるためです。オブジェクトではない値を指定すると、Qwikは状態の新しいオブジェクトを作成し、指定された値を新しいキー
{ _data: <your_value> }
に追加します。Qwikは、これが発生した場合、
dev
モードでブラウザのコンソールに警告も表示します。
リクエストイベント
onRequest
、onGet
、onPost
などの各リクエストハンドラーには、ハンドラーの最初の引数としてRequestEvent
オブジェクトが渡されます。RequestEvent
オブジェクトには、サーバーのリクエストとレスポンスの値を取得および設定するためのユーティリティ関数とプロパティが含まれています。このオブジェクトには、次のプロパティが含まれています。
basePathname
:リクエストのベースパス名。ビルド時に構成できます。デフォルトは/
です。cacheControl
:Cache-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/json
、application/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
の場合、同じパスパラメータslug
がproduct-name
、nome-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は以下もサポートしています
タイプセーフルーティング
これらについては後で説明します。