Rewrites

Sometimes you want to redirect a user from the current page to another page, but you want to keep the current URL in the browser history.

Let's say a user tries to access an article which is indexed by its name, e.g /articles/qwik-is-very-quick. but in our code, we access it by its id, on our directory structure.

src/routes/articles/
├── [id]
├─── index.tsx
src/routes/plugin@article-rewrite.tsx
import type { RequestHandler } from '@qwik.dev/router';
 
export const onRequest: RequestHandler = async ({ url, rewrite, internalRequest }) => {
  if (internalRequest) {
    return;
  }
 
  const pattern = /^\/articles\/(.*)$/;
  // Detects /articles/<article-name>, returns null if url does not match the pattern.
  const match = url.pathname.match(pattern);
  if (match) {
    const articleName = match[1];
    const { id } = await db.getArticleByName(articleName);
    throw rewrite(`/articles/${id}`);
  }
};

Check internalRequest before applying broad rewrite rules. Qwik Router uses internal JSON requests for route loader fetches and fetch-based action submissions, and those requests must keep their internal routing behavior.

Rewrites and client navigation

A rewrite() happens on the server, so on a client navigation the router can't know which route a rewritten URL renders. When the URL has no page of its own, only matches a dynamic ([param]) or wildcard route, or matches a route without a component, the router asks the server where the URL finally routes to (q-route.json), then renders that route while keeping the original URL.

A static route that matches the URL exactly is always rendered as-is, so don't keep a page on a URL that your middleware rewrites.

The answer is sent with the response headers your middleware has set when it calls rewrite(), and private if it set no Cache-Control, like loaders. To cache it (browser, CDN, ...), set the headers before rewriting. If the rewrite depends on the user (cookies, auth), keep it private or Vary accordingly:

export const onRequest: RequestHandler = async ({ url, rewrite, cacheControl, headers }) => {
  const match = url.pathname.match(/^\/articles\/(.*)$/);
  if (match) {
    const { id } = await db.getArticleByName(match[1]);
    cacheControl({ maxAge: 300, staleWhileRevalidate: 3600 }, 'CDN-Cache-Control');
    headers.set('Cache-Tag', `article-${id}`);
    throw rewrite(`/articles/${id}`);
  }
};

The rewrite() function, which was destructured in the RequestHandler function arguments, is invoked with a pathname string.

throw rewrite(`/articles/777/`);

Contributors

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

  • omerman