Skip to content

Symfony UX Router

Edit this page

EXPERIMENTAL This component is currently experimental and is likely to change, or even change drastically.

Symfony UX Router is a Symfony bundle that generates the URLs of your Symfony routes in JavaScript, with the same result as the path() and url() Twig functions. With TypeScript, route names and parameters are type-checked. It is part of the Symfony UX initiative.

Installation

Install the bundle using Composer and Symfony Flex:

1
$ composer require symfony/ux-router

After you install the bundle, the Symfony Flex recipe creates the following file:

1
2
3
4
5
// assets/router.js
import { createRouter } from '@symfony/ux-router';
import { routes, defaultLocale } from '../var/routes/index.js';

export const { path, url } = createRouter({ routes, defaultLocale });

If you use TypeScript, rename it to router.ts to type-check route names and parameters.

Using with AssetMapper

When installing with AssetMapper, Flex will add a new item to your importmap.php file:

1
2
3
'@symfony/ux-router' => [
    'path' => './vendor/symfony/ux-router/assets/dist/router_controller.js',
],

You have nothing else to configure: the bundle registers the dump directory and the @symfony/ux-router package in AssetMapper.

Using with Reprise

If you're using Symfony Reprise (Vite or Rsbuild), install your assets and restart your dev server:

1
2
$ npm install --force
$ npm run dev

The Reprise plugin needs no extra option: your bundler resolves the ../var/routes/index.js import of assets/router.js like any other file.

Using with Webpack Encore

If you're using Webpack Encore, install your assets and restart Encore:

1
2
$ npm install --force
$ npm run watch

Note

For more complex installation scenarios, you can install the JavaScript assets through the @symfony/ux-router npm package.

Usage

When warming up the Symfony cache, the routes you expose are dumped as JavaScript into the var/routes/ directory. TypeScript types are dumped next to them. In the dev environment, they are dumped again on the next request after a route changes.

Exposing routes

No route is exposed by default. A route is dumped to JavaScript when its name matches one of the routes patterns:

1
2
3
4
5
# config/packages/ux_router.yaml
ux_router:
    routes:
        - 'app_*'
        - '!app_admin_*'

Patterns support the * wildcard, and a leading ! excludes the matching routes. The * pattern alone exposes every route, including internal ones like the profiler's.

A localized route is exposed when its own name (app_about) or the name of one of its variants (app_about.en) matches a pattern.

You can also expose (or hide) a single route with its expose option, which always wins over the patterns:

1
2
#[Route('/search', name: 'search', options: ['expose' => true])]
public function search(): Response

Caution

Exposed routes are readable by anyone loading your JavaScript. Do not expose routes whose existence must stay secret.

Manually warming the routes cache

Routes are automatically dumped when warming up the Symfony cache. If you only need to refresh the JavaScript routes without warming up the whole cache, run:

1
$ php bin/console ux:router:warm-cache

Disabling TypeScript types dump

By default, TypeScript types are dumped next to the JavaScript routes. They give you autocompletion and type-checking when calling path() and url().

AssetMapper ignores them, so you can stop dumping them in production:

1
2
3
4
# config/packages/ux_router.yaml
when@prod:
    ux_router:
        dump_typescript: false

Keep them if you build your assets with Reprise or Webpack Encore and TypeScript: the build needs them to type-check your code.

Generating URLs

If you use the Symfony Flex recipe, import the path() and url() functions from the assets/router.js file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// assets/my_file.js
import { path, url } from './router.js';

path('app_blog_show', { slug: 'hello', _query: { page: 2 }, _fragment: 'comments' });
// '/blog/hello?page=2#comments'

path('app_blog_show', { slug: 'hello' }, true);
// relative path, e.g. 'blog/hello'

url('app_blog_show', { slug: 'hello' });
// 'https://example.com/blog/hello'

url('app_blog_show', { slug: 'hello' }, true);
// '//example.com/blog/hello'

Query string parameters go in _query. Optional parameters equal to their default value are omitted, as in PHP.

Localized routes

Call localized routes by their name: the current locale picks the right variant:

1
2
path('app_about');                    // '/about' when <html lang="en">
path('app_about', { _locale: 'fr' }); // '/a-propos'

The current locale is the first one found in:

  1. the _locale parameter given to path() or url();
  2. the _locale parameter of the context given to createRouter();
  3. the lang attribute of <html> (fr-FR becomes fr_FR);
  4. the defaultLocale option of createRouter();
  5. en.

The dumped var/routes/index.js file exports the framework.default_locale of your application as defaultLocale.

Configuring the request context

The host, scheme and port come from the current page. Pass a context to override them, for example when your application is served from a sub-directory:

1
2
3
4
5
// assets/router.js
export const { path, url } = createRouter({
    routes,
    context: { baseUrl: '/my-app' },
});

Handling errors

RouteNotFoundError, MissingMandatoryParametersError and InvalidParameterError are thrown in the same situations as in PHP, with almost the same messages. They are exported by @symfony/ux-router.

There are two differences from PHP. InvalidParameterError shows the requirement as JavaScript checks it ([^/]+ instead of [^/]++). A requirement that JavaScript cannot express, like \A or [[:alpha:]], is not checked.

Configuration

1
2
3
4
5
6
7
8
9
10
11
# config/packages/ux_router.yaml
ux_router:
    # The directory where routes and TypeScript types are dumped.
    # If you change it, also change the import in assets/router.js
    dump_directory: '%kernel.project_dir%/var/routes'

    # Whether TypeScript types are dumped next to the routes
    dump_typescript: true

    # Route name patterns to expose, prefix with "!" to exclude
    routes: []

Q&A

What about bundle size?

All the routes you expose are included in the generated var/routes/index.js file, which means they are included in your final JavaScript bundle. Use the routes patterns and the expose option to expose only the routes your JavaScript calls.

Backward Compatibility promise

This bundle aims to follow the same Backward Compatibility promise as the Symfony framework: https://symfony.com/doc/current/contributing/code/bc.html

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version