Advanced Routing
404 Page Handling
It's possible for any user to visit a URL that doesn't exist on your site. For example, if a user visits https://example.com/does-not-exist
, then the server should respond with a 404 HTTP status code, and the page should have at least some sort of explanation, rather than just a blank page.
For any given route, Qwik City can choose how to best handle the 404 response for the user, whether it's with the default 404 page, a custom 404 page, or a dynamically generated 404 page.
Default 404 Page
Rather than showing a blank page, by default Qwik City provides a generic 404 page for any route that isn't handled. The default 404 page is rendered as a fallback when a custom 404 page was not found. Qwik City's default page is alright and all, but we recommend providing a custom 404 so the user can be given a better experience, such as providing the common header and navigation so the user can find the page they're looking for.
Custom 404 Page
Instead of showing the generic (boring) 404 response, it's possible to provide a custom 404 page using the same familar layouts as the rest of your site.
To create a custom 404 page, add a 404.tsx
file to the root src/routes
directory.
src/
โโโ routes/
โโโ 404.tsx # Custom 404
โโโ layout.tsx # Default layout
โโโ index.tsx # https://example.com/
In the example above, the 404.tsx
page will also use the layout.tsx
layout, since it is a sibling of the layout in the same directory.
Additionally, using Qwik City's directory-based routing allows for custom 404 pages to be created at different paths. For example, if src/routes/account/404.tsx
was also added to the structure, then the custom account 404 page would only be applied to the /account/*
routes, while all other 404s would use the root 404.tsx
page.
Note: During development and in preview mode, the custom 404 pages will be not render, but instead the default Qwik City 404 page will show. However, when building the app for production, the custom 404 page will be statically generated as a static
404.html
file.
src/
โโโ routes/
โโโ account/
โ โโโ 404.tsx # Custom Account 404
โ โโโ index.tsx # https://example.com/account/
โโโ 404.tsx # Custom 404
โโโ layout.tsx # Default layout
โโโ index.tsx # https://example.com/
Also note that custom 404 pages are statically generated at build-time, meaning they end up as static 404.html
file, rather than each being a server-side rendered page. Creating static 404.html
pages helps ensure your HTTP server isn't bothered server-side rendering 404 pages and eating up resources.
Dynamic 404 Page
When rendering a page, the default is to always respond with a 200 HTTP status code, which tells the browser all is good and the route you went to does exists. However, it's also possible to handle rendering the page, but manually setting the response status code to something other than 200, such as 404.
For example, let's say we have a product page with a URL such as https://example.com/product/abc
. The product page would be handled using src/routes/product/[id]/index.tsx
directory-based route, and [id]
is a dynamic param in the URL.
In this example, id
is used as a key to load the product data from the database. When the product data is found, great, we'll correctly render data. However, if the product data is not found in the database, we can still handle rendering this page, but instead responding with a 404 HTTP status code.
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useProductLoader = routeLoader$(async ({ params, status }) => {
// Example database call using the id param
// The database could return null if the product is not found
const data = await productDatabase.get(params.id);
if (!data) {
// Product data was not found
// Set the status code to 404
status(404);
}
// return the data (which may be null)
return data;
});
export default component$(() => {
// get the product data from the loader
const product = useProductLoader();
if (!product.value) {
// no product data found
// so render our own custom product 404
return <p>Sorry, looks like we don't have this product.</p>;
}
// product data was found, so let's render it
return (
<div>
<h1>{product.value.name}</h1>
<p>{product.value.price}</p>
<p>{product.value.description}</p>
</div>
);
});
Grouped Layouts
Common purpose routes are often placed into directories so they can share layouts, and so related source files are logically grouped next to each other. However, it may be desirable that the directory, which was used to group similar files and share layouts, is excluded from the public-facing URL. This is where "grouped" layouts come in (also referred to as a "pathless" layout route).
By surrounding any directory name with parentheses, such as (name)
, then the directory name itself will not be included in the URL pathname.
For example, let's say an app placed all account routes together in a directory, however, /account/
could be dropped from the URL for cleaner, shorter URLs. In the example below, notice that the paths are within the src/routes/(account)/
directory. However, the URL paths exclude (account)/
.
src/
โโโ routes/
โโโ (account)/ # Notice the parentheses
โโโ layout.tsx # Shared account layout
โโโ profile/
โโโ index.tsx # https://example.com/profile
โโโ settings/
โโโ index.tsx # https://example.com/settings
Named Layout
At times related routes need to have drastically different layouts from their siblings. It is possible to define multiple layouts for different sibling routes. A single default layout and any number of named layouts. The child route can then request a specific named-layout.
Qwik City defines the convention that layouts are within src/routes
and the filename starts with layout
. That's why the default layout is named layout.tsx
. A named layout also starts with layout
followed by a dash -
and a unique name, such as layout-narrow.tsx
.
src/
โโโ routes/
โโโ contact/
โ โโโ index@narrow.tsx # https://example.com/contact (Layout: layout-narrow.tsx)
โโโ layout.tsx # Default layout
โโโ layout-narrow.tsx # Default named layout
โโโ index.tsx # https://example.com/ (Layout: layout.tsx)
https://example.com/
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ src/routes/layout.tsx โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ src/routes/index.tsx โ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
https://example.com/contact
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ src/routes/layout-narrow.tsx โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ src/routes/contact/index@narrow.tsx โ โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Nested Layout
Most times it is desirable to nest layouts into each other. A page's content can be nested in numerous wrapping layouts, which is determined by the directory structure.
src/
โโโ routes/
โโโ layout.tsx # Parent layout
โโโ about/
โโโ layout.tsx # Child layout
โโโ index.tsx # https://example.com/about
In the above example, there are two layouts that apply themselves around the /about
page component.
src/routes/layout.tsx
src/routes/about/layout.tsx
In this case, the layouts will nest each other with the page within each of them.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ src/routes/layout.tsx โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ src/routes/about/layout.tsx โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ src/routes/about/index.tsx โ โ โ
โ โ โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import { component$, Slot } from '@builder.io/qwik';
export default component$(() => {
return (
<main>
<Slot /> {/* <== Child layout/route inserted here */}
</main>
);
});
import { component$, Slot } from '@builder.io/qwik';
export default component$(() => {
return (
<section>
<Slot /> {/* <== Child layout/route inserted here */}
</section>
);
});
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <h1>About</h1>;
});
The above example would render the html:
<main>
<section>
<h1>About</h1>
</section>
</main>
plugin.ts
plugin.ts
and plugin@<name>.ts
files can be created in the root of the src/routes
directory to handle any incoming request before even the root layout executes.
You can have multiple plugin.ts
files, each with a different name. For example, plugin@auth.ts
and plugin@security.ts
. The @<name>
is optional and and it's only used for developers to help identify the plugin.
Requests handlers like onRequest
, onGet
, onPost
, etc. are called before server$
functions are executed.