- Added important notes regarding ES Module exports and route parameter access in backend scripts. - Introduced a new section on data binding methods, detailing how to call backend script methods from `.json` configurations. - Included examples demonstrating the correct usage of request parameters and common pitfalls to avoid, improving clarity for developers.
5.6 KiB
Routing
SUI supports file-system based routing with dynamic route parameters and URL rewriting.
File-System Routing
Pages are organized in directories, with each directory containing a page's files:
/pages/
├── index/
│ ├── index.html
│ ├── index.css
│ └── index.ts
├── about/
│ ├── about.html
│ └── about.css
└── users/
├── users.html
└── [id]/ # Dynamic route
├── [id].html
├── [id].css
└── [id].ts
Dynamic Routes
Use square brackets [param] to create dynamic route segments:
| Directory Structure | URL Pattern | Example URL |
|---|---|---|
/users/[id]/ |
/users/:id |
/users/123 |
/posts/[slug]/ |
/posts/:slug |
/posts/hello-world |
/[category]/[id]/ |
/:category/:id |
/electronics/456 |
Accessing Route Parameters
In HTML templates - Use $param:
<h1>User ID: {{ $param.id }}</h1>
<p>Category: {{ $param.category }}</p>
In .json configuration:
{
"userId": "$param.id",
"$user": {
"process": "models.user.Find",
"args": ["$param.id"]
}
}
In backend scripts - Via Request object:
function GetRecord(request: Request): any {
const id = request.params.id;
return Process("models.record.Find", id);
}
Note
:
$paramis NOT available as a global variable in backend scripts. You must access route parameters through therequest.paramsobject.
URL Rewriting
SUI pages require URL rewriting to map clean URLs to .sui page files. Configure rewrite rules in app.yao:
{
"public": {
"rewrite": [
{ "^\\/assets\\/(.*)$": "/assets/$1" },
{ "^\\/users\\/([^\\/]+)$": "/users/[id].sui" },
{ "^\\/(.*)$": "/$1.sui" }
]
}
}
Rewrite Rule Syntax
Each rule is a JSON object with a regex pattern as the key and the target path as the value:
{ "REGEX_PATTERN": "TARGET_PATH" }
- REGEX_PATTERN: A regular expression to match the incoming URL
- TARGET_PATH: The internal path to route to, can use capture groups (
$1,$2, etc.)
Rule Processing Order
Rules are processed in order from top to bottom. The first matching rule wins. Always place more specific rules before general ones.
Common Patterns
Static Assets (Passthrough)
{ "^\\/assets\\/(.*)$": "/assets/$1" }
Passes asset requests directly without modification.
Simple Dynamic Route
{ "^\\/users\\/([^\\/]+)$": "/users/[id].sui" }
Maps /users/123 to /users/[id].sui, making 123 available as $param.id.
Nested Dynamic Route
{
"^\\/users\\/([^\\/]+)\\/posts\\/([^\\/]+)$": "/users/[id]/posts/[postId].sui"
}
Maps /users/123/posts/456 to the nested page, with $param.id = "123" and $param.postId = "456".
Catch-All for SUI Pages
{ "^\\/(.*)$": "/$1.sui" }
Maps any URL to its corresponding .sui file. Place this last as a fallback.
Specific Page Override
{ "^\\/dashboard\\/login(.*)$": "/dashboard/login.sui" },
{ "^\\/dashboard\\/(.*)$": "/dashboard/[id].sui" }
The login page is matched first (specific), then other dashboard pages use dynamic routing.
Complete Example
{
"public": {
"rewrite": [
// Static assets - passthrough
{ "^\\/assets\\/(.*)$": "/assets/$1" },
{ "^\\/images\\/(.*)$": "/images/$1" },
// Specific pages (before dynamic routes)
{ "^\\/blog\\/new$": "/blog/new.sui" },
{ "^\\/blog\\/([^\\/]+)\\/edit$": "/blog/[id]/edit.sui" },
// Dynamic routes
{ "^\\/blog\\/([^\\/]+)$": "/blog/[id].sui" },
{
"^\\/users\\/([^\\/]+)\\/posts\\/([^\\/]+)$": "/users/[id]/posts/[postId].sui"
},
{ "^\\/users\\/([^\\/]+)$": "/users/[id].sui" },
// Fallback - must be last
{ "^\\/(.*)$": "/$1.sui" }
]
}
}
Regex Tips
| Pattern | Matches | Description |
|---|---|---|
([^\\/]+) |
Any segment | Matches characters until next / |
(.*) |
Everything | Matches any characters including / |
(\\d+) |
Numbers only | Matches numeric IDs |
([a-z-]+) |
Lowercase + hyphen | Matches slugs like hello-world |
Debugging Rewrite Rules
- Check the server logs for route matching information
- Ensure regex escaping is correct (double backslashes in JSON:
\\/for/) - Test specific URLs to verify capture groups work correctly
- Remember that the
.suiextension is internal - users access pages without it
Route Parameters in Different Contexts
| Context | Access Method | Example |
|---|---|---|
| HTML Template | {{ $param.id }} |
<h1>{{ $param.id }}</h1> |
.json Config |
"$param.id" |
"userId": "$param.id" |
| Backend Script | request.params.id |
const id = request.params.id; |
| Frontend Script | Read from DOM | document.body.dataset.id |
Frontend Access Pattern
Since frontend scripts run in the browser, route params aren't directly available. Pass them via data attributes:
HTML:
<div id="page" data-id="{{ $param.id }}">
<!-- content -->
</div>
Frontend TypeScript:
const pageEl = document.getElementById("page");
const id = pageEl?.dataset.id;