ファイルベースルーティングの仕様と使い方。
基本概念
ファイルの配置がそのままURLパスになる。ルーター設定は不要。
lib/app/page.ex → /
lib/app/about/page.ex → /about
lib/app/users/page.ex → /users
lib/app/users/[id]/page.ex → /users/:id静的ルーティング
基本的な対応
| ファイルパス | URL |
|---|---|
app/page.ex | / |
app/about/page.ex | /about |
app/docs/guide/page.ex | /docs/guide |
インデックスページ
ディレクトリ配下の page.ex が該当パスを担当する。
app/users/page.ex → /users(ユーザー一覧)
app/users/[id]/page.ex → /users/123(ユーザー詳細)動的ルーティング
ブラケット記法
[パラメータ名] でURLパラメータを受け取る。
app/users/[id]/page.ex → /users/123, /users/abc など
app/posts/[slug]/page.ex → /posts/hello-world などparamsへのアクセス
mount/2 の第1引数で受け取る。パスパラメータとクエリパラメータが両方含まれる:
defmodule MyApp.Users.Id do
use Dialup.Page
# /users/123?tab=posts にアクセス
def mount(params, assigns) do
id = params["id"] # "123"(パスパラメータ)
tab = params["tab"] # "posts"(クエリパラメータ)
user = Users.get!(id)
{:ok, %{user: user, tab: tab || "profile"}}
end
endテンプレートからは @params でもアクセスできる:
<p>現在のタブ: {@params["tab"]}</p>クエリパラメータ
クエリ文字列(?key=value)は自動的に params に含まれる。複数パラメータにも対応:
/search?q=elixir&page=2&sort=date
→ params = %{"q" => "elixir", "page" => "2", "sort" => "date"}パスパラメータとクエリパラメータで同名のキーがある場合、パスパラメータが優先される。
パターンマッチ
異なるパラメータ値で分岐できる:
# /users/new → 新規作成画面
def mount(%{"id" => "new"}, assigns) do
{:ok, assigns |> overwrite(%{mode: :new, user: nil})}
end
# /users/:id → 編集画面
def mount(%{"id" => id}, assigns) do
user = Users.get!(id)
{:ok, assigns |> overwrite(%{mode: :edit, user: user})}
endレイアウトの継承
レイアウトファイルの配置
app/layout.ex # 全ページの共通レイアウト
app/users/layout.ex # /users 以下のレイアウト
app/users/[id]/layout.ex # /users/:id 以下のレイアウト(あれば)レイアウトの適用ルール
URL /users/123/profile の場合:
app/layout.ex(最上位)app/users/layout.ex(中間)app/users/[id]/page.exまたはapp/users/profile/page.ex(該当ページ)
レイアウトは上から順にネストされる。
ルーティングの優先順位
- 静的ルートを先に探索(完全一致)
- 動的ルートにフォールバック(パターンマッチ)
- マッチしない場合は404
app/users/new/page.ex → 静的: /users/new
app/users/[id]/page.ex → 動的: /users/:id
/users/new → 静的ルートが優先
te/users/123 → 動的ルートがマッチコロケーション CSS
page.ex / layout.ex と同じディレクトリに同名の .css ファイルを置くと、コンパイル時に自動検出・スコーピングされる。
app/
├── layout.ex
├── layout.css ← 全ページ共通スタイル
├── page.ex
├── page.css ← / のみに適用
└── docs/
├── layout.css ← /docs 配下全ページに適用
└── page.css ← /docs トップのみに適用スコーピングの仕組み
モジュール名の MD5 先頭 7 文字から d-xxxxxxx というスコープクラスを生成し、CSS をネイティブ CSS nesting でラップする:
/* page.css に書いたもの → コンパイル後 */
.d-a1b2c3 {
h1 { color: blue; }
.card { padding: 1rem; }
}レンダリング時にそのページの HTML が <div class="d-a1b2c3">...</div> でラップされる。CSS は <style data-dialup-css> としてその直前に注入される。
layout.css のカスケード
layout.css のスコープ div は全子ページの HTML を包むため、layout.css に書いたスタイルは配下の全ページに自然にカスケードする:
<div class="d-layout"> <!-- app/layout.css のスコープ -->
<header>...</header>
<div class="d-docs-layout"> <!-- docs/layout.css のスコープ -->
<div class="d-docs-page"> <!-- docs/page.css のスコープ -->
<h1>Docs</h1>
</div>
</div>
</div>注意事項
- CSS が存在しないページでは
<style>タグも wrapper div も出力されない(オーバーヘッドゼロ) .css変更時は@external_resource経由で対応する.exが自動再コンパイルされ、ホットリロードが動作する- CSS nesting はモダンブラウザ 96%+ でサポート(Chrome 120+, Firefox 117+, Safari 17.2+)
レイアウトのオプトアウト
特定のページでレイアウト継承を無効にし、全画面表示にしたい場合は @layout false を指定する。
defmodule Dialup.App.Login.Page do
use Dialup.Page
@layout false
def render(assigns) do
~H"""
<div class="fullscreen-login">
<h1>ログイン</h1>
<form ws-submit="login">
<input name="email" placeholder="Email" />
<input name="password" type="password" />
<button type="submit">ログイン</button>
</form>
</div>
"""
end
end@layout false を指定すると、親ディレクトリの layout.ex は適用されず、ページの HTML が直接 Shell に挿入される。コロケーション CSS(page.css)は @layout false でも適用される。
エラーページ
カスタムエラーページ
app/ ディレクトリに error.ex を置くと、404 や 500 エラー時にユーザー定義のエラーページが表示される。layout.ex と同様にディレクトリ階層で継承される。
app/
├── layout.ex # 全ページ共通レイアウト
├── error.ex # 全ページ共通エラーページ
├── page.ex
└── admin/
├── layout.ex # /admin 配下の共通レイアウト
├── error.ex # /admin 配下のエラーページ(より具体的)
└── page.exerror.ex の書き方
use Dialup.Error を使い、render/2 でステータスコードごとに表示を分岐する。
defmodule Dialup.App.Error do
use Dialup.Error
def render(404, assigns) do
~H"""
<div class="error-page">
<h1>404</h1>
<p>ページが見つかりませんでした</p>
<a ws-href="/">ホームへ戻る</a>
</div>
"""
end
def render(500, assigns) do
~H"""
<div class="error-page">
<h1>500</h1>
<p>サーバーエラーが発生しました</p>
</div>
"""
end
def render(_status, assigns) do
~H"""
<div class="error-page">
<h1>{@status}</h1>
<p>エラーが発生しました</p>
</div>
"""
end
endエラーページの継承
リクエストパスに最も近い error.ex が選択される。
/admin/users/123で 404 →app/admin/error.exがあればそれ、なければapp/error.exerror.exが一つも存在しなければフレームワークのデフォルト表示が使われる
レイアウトとの関係
エラーページは通常のページと同様に layout に囲われて出力される。全画面のエラーページにしたい場合は @layout false を指定する。
defmodule Dialup.App.Error do
use Dialup.Error
@layout false # layout なしの全画面エラーページ
def render(404, assigns) do
~H"""
<div class="fullscreen-error">
<h1>404 Not Found</h1>
</div>
"""
end
endassigns に渡される情報
%{
status: 404, # HTTP ステータスコード
message: "Not Found" # ステータスメッセージ
}500 エラーの場合、開発環境(Mix.env() == :dev)ではさらに exception と stacktrace が含まれる。
コロケーション CSS
error.css を error.ex と同じディレクトリに配置すると、ページや layout と同様に自動スコーピングが適用される。
制限事項
- 正規表現やカスタムパターンマッチは不可
[id]はURL全体のセグメントにマッチ(/を含まない)- オプショナルパラメータは不可(
/usersと/users/:idは別ファイルが必要)