Routing in Qwik City is file-system based like Next.js, SvelteKit, SolidStart or Remix. Files and directories in the src/routes have a role in the routing of your application.

  • ๐Ÿ“‚ Directories: Describe the URL segments to match by the router.
  • ๐Ÿ“„ index. files: Page/endpoint.
  • ๐Ÿ–ผ๏ธ layout. files: Nested layout/middleware.

Directory-based routing

Only the directory names are used to match the incoming requests to pages/endpoints.

For example, if you have a file at src/routes/some/path/index.tsx, it will be mapped to the URL path

Directory layout
โ””โ”€โ”€ routes/
    โ”œโ”€โ”€ contact/
    โ”‚   โ””โ”€โ”€ index.mdx         #
    โ”œโ”€โ”€ about/
    โ”‚   โ””โ”€โ”€          #
    โ”œโ”€โ”€ docs/
    โ”‚   โ””โ”€โ”€ [id]/
    โ”‚       โ””โ”€โ”€ index.ts      #
    โ”‚                         #
    โ”œโ”€โ”€ [...catchall]/
    โ”‚   โ””โ”€โ”€ index.tsx         #
    โ””โ”€โ”€ layout.tsx            # This layout is used for all pages
  • [id] is a directory that represents a dynamic route segment, in this example id is the string parameter accessible by getLocation()
  • [...catchall] is a directory that represents a dynamic catch-all route, in this example catchall is the string parameter accessible by getLocation().params.catchall.
  • index.tsx|mdx files are the pages/endpoints.
  • layout.tsx files are the layouts.

Dynamic route segments

Special named directories with square brackets, such as [paramName] and [...catchAll] can be used to match route segments which are dynamic:

Directory layout
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)
Directory layout
โ””โ”€โ”€ routes/
    โ”œโ”€โ”€ blog/
    โ”‚   โ””โ”€โ”€ index.tsx         #
    โ”œโ”€โ”€ post/
    โ”‚   โ””โ”€โ”€ [...all]/
    โ”‚       โ””โ”€โ”€ index.tsx     #
    โ””โ”€โ”€ user/
        โ””โ”€โ”€ [username]/
            โ””โ”€โ”€ index.tsx     #

The folder [username] can be any of the thousands of users that you have in your database. It would be impractical to create a route for each user. Instead, you need to define a Route Parameter (a part of the URL) that will be used to extract the [username].

import { component$ } from '';
import { useLocation } from '';
export default component$(() => {
  const loc = useLocation();
  return <div>Hello {loc.params.username}!</div>;

index. files

Inside the src/routes directory, all files named index are considered pages/endpoints, Qwik supports the following extensions: .ts, .tsx, .md and .mdx.

Pages/endpoints are the leaf nodes of the routing tree, ie, the modules that will handle the request and return an HTTP response.

Page index.tsx

When index.tsx or index.ts exports a Qwik component as the default export, Qwik City will render the component and return an HTML response as a webpage.

import { component$ } from '';
export default component$(() => {
  return <h1>Hello World</h1>;

Endpoint index.ts

A index.ts can also access the HTTP request directly and return a raw HTTP response without involving any Qwik Component. This is done by exporting an onRequest method or onGet, onPost, onPut, onDelete depending on if you only want to handle a specific request given its HTTP method.

import type { RequestHandler } from '';
export const onGet: RequestHandler = ({ json }) => {
  json(200, { message: 'Hello World' });

Notice that in the last example, there is no default export. This is because we are not rendering a Qwik component, but rather we are handling the request directly and returning a JSON response. This is useful to implement RESTful APIs or any other type of HTTP endpoint.

Page + Endpoint

As you can see in Qwik City there is no clear separation between pages and endpoints, in both cases, it's a index.tsx file that exports a Qwik component or an onRequest method. However, it's possible to combine both approaches. For example, you can export a onRequest method that will handle the request, and then render a Qwik component.

import { component$ } from '';
import type { RequestHandler } from '';
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>;

In this example, a request handle will always set the Cache-Control header to private and the page will be rendered as an HTML page, but if the request contains a format=json query param, the endpoint will return a JSON response instead.

layout. files

Layout modules are very similar to index files, both can handle requests and render Qwik components, however, layouts are designed to work like a middleware, allowing to share UI and request handling (middleware) to a set of routes.

Usually, different pages need some common request handling and share some UI. For example, picture a dashboard site where all the pages are under the /admin/* directory:

  • Shared request handling: The request cookies need to be validated before even rendering the page, otherwise, render a blank 401 page.
  • Shared UI: All pages share a common header showing the user's name and profile picture.

Instead of repeating the same code in each route, we can use layouts to automatically reuse common parts, and also to add middleware to the route.

Take this src/routes directory as an example:

Directory layout
โ””โ”€โ”€ 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

Middleware layouts

Since layouts can implement request handling with onRequest or onGet, onPost, onPut, onDelete, they can be used to implement middleware, for example, to validate the request cookies before rendering the page.

For the route, the onRequest methods will be executed in the following order:

  1. src/routes/layout.tsx's onRequest
  2. src/routes/admin/layout.tsx's onRequest
  3. src/routes/admin/index.tsx's component

Nested layouts

Layouts also provide a way to add common UI to the rendered page. For example, if we want to add a common header to all the routes, we can add a Header component to the root layout.

For the given example, the Qwik components will be rendered in the following order:

  1. src/routes/layout.tsx's component
  2. src/routes/admin/layout.tsx's component
  3. src/routes/admin/index.tsx's component
    <AdminPage />

SPA navigation

Qwik provides the <Link> component and the useNavigate() hook to refresh and navigate between pages.

The Link is usually the recommend way to navigate because it uses the HTML <a> tag, which is the most accessible way to navigate between pages.

However, if you need to navigate programmatically, you can use the useNavigate() hook.

import { component$ } from '';
import { Link, useNavigate } from '';
export default component$(() => {
  const nav = useNavigate();
  return (
      <Link href="/about">About (prefered)</Link>
      <button onClick$={() => nav('/about')}>About</button>


You can use the Link with the reload prop to refresh the page.

You can also call the nav() function from the useNavigate() hook, without any arguments.

import { component$ } from '';
import { Link, routeLoader$, useNavigate } from '';
export const useServerTime = routeLoader$(() => {
  // This will re-execute in the server when the page refreshes
export default component$(() => {
  const nav = useNavigate();
  const serverTime = useServerTime();
  return (
      <Link reload>Refresh (better accesibility)</Link>
      <button onClick$={() => nav()}>Refresh</button>
      <p>Server time: {serverTime.value}</p>

When the page refreshes, all the matching routeLoader$ and server handlers (onRequest) will reexecute in the server and the UI will re-render accordingly.

While refreshing the page, the isNavigating boolean from useLocation() will be true until the page is fully rendered.

Request Event

Each request handler, such as onRequest, onGet, onPost, etc., are passed in a RequestEvent object as the first argument to the handler. The RequestEvent object contains utility functions and properties to get and set values to the server's request and response. This object contains the following properties:

  • basePathname: The base pathname of the request, which can be configured at build time. Defaults to /.
  • cacheControl: Convenience function to set the Cache-Control response header.
  • cookie: HTTP request and response cookies. Use the get() method to retrieve a request cookie value. Use the set() method to set a response cookie value.
  • env: Platform provided environment variables.
  • error: When called, the response will immediately end with the given status code. This could be useful to end a response with 404, and use the 404 handler in the routes directory. See Status codes for which status code should be used.
  • getWritableStream: Low-level access to write to the HTTP response stream. Once getWritableStream() is called, the status and headers can no longer be modified and will be sent over the network.
  • headers: HTTP response headers.
  • html: Convenience method to send an HTML body response. The response will be automatically set the Content-Type header totext/html; charset=utf-8. An html() response can only be called once.
  • json: Convenience method to JSON stringify the data and send it in the response. The response will be automatically set the Content-Type header toapplication/json; charset=utf-8. A json() response can only be called once.
  • locale: Which locale the content is in. The locale value can be retrieved from selected methods using getLocale().
  • method: HTTP request method value.
  • next: Call the next request handler. This is useful for middleware.
  • params: URL path params which have been parsed from the current url pathname segments. Use query to instead retrieve the query string search params.
  • parseBody: This method will check the request headers for a Content-Type header and parse the body accordingly. It supports application/json, application/x-www-form-urlencoded, and multipart/form-data content types. If the Content-Type header is not set, it will return null.
  • pathname: URL pathname value. Does not include the protocol, domain, query string (search params) or hash.
  • platform: Platform specific data and functions.
  • query: URL query string URLSearchParams value. Use params to instead retrieve the route params found in the url pathname.
  • redirect: URL to redirect to. When called, the response will immediately end with the correct redirect status and headers. See Redirects for which status code should be used.
  • request: HTTP Request.
  • send: Send a body response. The Content-Type response header is not automatically set when using send() and must be set manually. A send() response can only be called once.
  • sharedMap: Shared Map across all the request handlers. Every HTTP request will get a new instance of the shared map. The shared map is useful for sharing data between request handlers.
  • status: HTTP response status code. Sets the status code when called with an argument. Always returns the status code, so calling status() without an argument will can be used to return the current status code.
  • text: Convenience method to send an text body response. The response will be automatically set the Content-Type header totext/plain; charset=utf-8. An text() response can only be called once.
  • url: HTTP request URL.

Advanced routing

Qwik City also supports:

These are discussed later.


Thanks to all the contributors who have helped make this documentation better!

  • manucorporat
  • nnelgxorz
  • the-r3aper7
  • Oyemade
  • mhevery
  • adamdbradley
  • wtlin1228
  • AnthonyPAlicea