url()
url() wraps a default value and keeps the variable in the query string.
The result is a page whose state can be linked to, bookmarked and shared - filters, search terms, the current page number.
<script>
let search = url('')
</script>
<input bind:value={search} placeholder="Type, and watch the address bar" />
<p>Searching for: {search}</p>
<style>
input {
width: 100%;
max-width: 22rem;
}
</style>Searching for:
The variable's name is the parameter name, so that writes ?search=hello. It
reads the URL on mount, writes it on every assignment, and follows the back and forward
buttons.
Writes use replaceState, so typing does not fill the history with one entry
per keystroke.
Pass a name first to use a different parameter:
let searchQuery = url('q', '') // ?q=
let currentPage = url('p', 1) // ?p=
Types
The default value decides how the parameter is read back:
| Default | In the URL | Read back as |
|---|---|---|
url('') |
?q=hello |
The string, unchanged |
url(1) |
?page=3 |
Number(). A blank or unparseable value falls back to the default. |
url(false) |
?dark=true |
True only for the exact text true |
url([]) |
?tags=a&tags=b |
Every value of that name, as an array |
url({}) |
URL-encoded JSON | JSON.parse(), falling back to the default if it fails |
Clean URLs
A value equal to its default is removed from the URL, so a page sitting on its defaults has
no query string at all rather than ?search=&page=1.
<script>
let page = url(1)
const totalPages = 10
</script>
<button disabled={page <= 1} onclick="page--">Previous</button>
<span>Page {page} of {totalPages}</span>
<button disabled={page>= totalPages} onclick="page++">Next</button>
<style>
button {
margin: 0 0.4rem;
padding: 0.35rem 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-secondary);
color: var(--text-primary);
cursor: pointer;
}
button:disabled {
opacity: 0.45;
cursor: default;
}
</style>
An empty string is a value, not an absence. A variable declared
url('newest') and then cleared writes ?sort= and reads back
'', so a cleared filter stays cleared across a reload. Only
null and undefined cannot be put in a URL at all.
A key that appears twice
?a=1&a=2 resolves to its first value -
"1" - and that is true of every scalar reader: a
url() variable and window.route.query.a give the same
answer, because both ask the platform's own URLSearchParams.get().
Use the array form when you want every repeat.
// URL: /list?a=1&a=2
let a = url('') // "1" - the first
let all = url([]) // ["1", "2"] - both, in URL order
route.query.a // "1" - the same rule
A repeated key is not exotic: a form with two same-named inputs, a link
built by string concatenation, or a redirect that appends a param that was
already there all produce one. The two spellings are still different URLs
to the router, so navigating from ?a=1&a=2 to
?a=2&a=1 is a real navigation even though both read
a the same way.
Text a URL cannot decode
% starts a two-hex-digit escape, so ?a=%zz is not
valid encoding. JAF hands it back as the literal text "%zz"
rather than throwing, and does the same for a path segment - /users/%zz
gives route.params.id === "%zz".
Dev builds warn, naming the reader and the segment. Write %25 for
a literal percent sign.
Related
- storage() - state that persists instead of travelling
- Dynamic Routes - state that lives in the path