Routing
Introduction
Every API call requires two given headers:
- X-OpenDxp-Api-Locale
- X-OpenDxp-Api-Site
Knowing that, this will:
- change the active request
localeanddefaultLocaleto the given locale value - apply given site value to OpenDXP's site resolver
- change the active request context host by using the
mainDomainof the given site value
This ensures, that every underlying url/route generation will use the right context, regardless of the api endpoint host!
Route Document
The "route document" is your starting point for an uri. It provides all necessary information to handle the requested uri.
Route Document Resolvers
Route entity
Resolve entities for your custom routes by creating a service and implement HeadlessBundle\RouteDocument\RouteEntityResolverInterface, tag it with headless.resolver.route_document.route_entity.
The built-in DataObjectRouteEntityResolver service can be used to resolve a data object for your (static) route.
Provide the route name, the route variable name (which holds the data object's id) and the data object class name as constructor arguments:
services:
my.headless.route_entity_resolver.my_object:
class: OpenDxp\Bundle\HeadlessBundle\RouteDocument\RouteEntity\DataObjectRouteEntityResolver
arguments: [ 'my_route_name', 'my_route_variable', 'MyDataObjectClass' ]
tags:
- { name: headless.resolver.route_document.route_entity }
Breadcrumb
The breadcrumb can be modified by defining a service with the headless.resolver.route_document.breadcrumb tag.
The service must implement HeadlessBundle\RouteDocument\BreadcrumbResolverInterface.
class CategoryBreadcrumbResolver implements BreadcrumbResolverInterface
{
public function supports(mixed $routeEntity, RouteDocument $routeDocument): bool
{
return $routeEntity instanceof CategoryInterface;
}
public function resolve(mixed $routeEntity, RouteDocument $routeDocument, Container $breadcrumb): void
{
if (!$routeEntity instanceof CategoryInterface) {
return;
}
$breadcrumb->addPage(
new Page\Document([
'id' => sprintf('category_%s', $routeEntity->getId()),
'label' => $routeEntity->getName($routeDocument->getLocale()) ?? $routeEntity->getKey()
])
);
}
}
Headless Routes
Routing
Within the headless concept, by default, it is not allowed to call "frontend" routes anymore. However, for certain cases you want a route to be available to the public, for example to deliver dynamically generated content (PDFs, RSS Feeds, ...) directly from your back-end.
Allowing routes
To allow specific routes, you need some additional configuration.
First, you need to add your route to the routes configuration.
With that, you're telling symfony, that this route can be accessed (whitelisted).
opendxp_headless:
routes:
- my_simple_route
Host Problematic
Since requests may happen within the api context – which runs under api.domain.tld – absolute routes will be generated with the wrong host.
So, when generating absolute urls for your route, the host will be probably wrong (because the generation happens withing the api context).
For that, you have to tell symfony router, which host you want to use.
By setting the host_context to the routes configuration, you are telling symfony, to force use the given host (context).
In order to make this work, it is crucial that corresponding entries are present in your OpenDXP site configuration.
The concrete host will be resolved within the router, which looks for a matching domain against the configured host_context in all configured domains of the OpenDXP site.
The special value _main_domain tells the router to resolve to the site's main domain.
For example, context services means, that the router searches for a domain starting with services.
As a result, the host context services requires a services.domain.tld subdomain.
opendxp_headless:
routes:
# for absolute urls, host will be resolved to a domain starting with services in site's domains.
- { name: my_simple_route, host_context: 'services' }
# for absolute urls, host will be resolved to the site's main domain.
- { name: my_frontend_route, host_context: '_main_domain' }
Configuration
# packages/headless.yaml
opendxp_headless:
# symfony routes, which should be publicly available
routes:
# host_context:
# This will transform your route host to the given context host.
# API calls are wrapped in api.your-main-domain.tld, therefor all upcoming routes will be rendered in a wrong context.
# Use the host_context to set the desired host, based on the given OpenDXP site.
# In this example, the host would change to: services.your-main-domain.tld/my-custom-service
- { name: my_custom_service_route, host_context: 'services' }
# It's also possible to define routes without context.
# For example, we'll look at the fos_js_routing_js route (Already whitelisted in OpenDxp\Bundle\HeadlessBundle, so it's not required to add it anymore).
# It should be available in backend, even if it's not in the /admin context:
# For this, adding the route name is fine since the host should be the same as set in given request
- fos_js_routing_js
Redirect
TBD
Theme
TBD
API Routes
TBD
Host Patterns
In rare cases, you may have to overwrite the default host patterns. These are the default values and requires to be written in a regex pattern:
opendxp_headless:
host_patterns:
api: '\.?api\.'
backend: '\.?backend\.'
services: '\.?services\.'
static: '\.?static\.'
Remote Routes
If you need to generate a remote route (The FE Route in your PWA/NextJs Application, which is probably unknown in the API/Backend Context), you need to define them:
opendxp_headless:
remote_routes:
my_remote_route_to_a_special_place_we_dont_know:
path: /{_locale}/my-special-route/{token}
Just use the headless router to generate it:
$myLink = $this->headlessRouter->generate(
'my_remote_route_to_a_special_place_we_dont_know',
[
'token' => 'a_not_so_secret_token'
],
$this->headlessRouter->extractRouteContextParameters($command->getHeadlessContextStack()),
null,
HeadlessRouterInterface::REMOTE_ROUTE
);
Asset Routing
Configure a global base URI that is prepended to all asset paths in API responses:
# packages/headless.yaml
opendxp_headless:
assets:
host: '%headless_asset_host%' # define it via parameter in your env based parameter (e.g. https://static.my-domain.test)
This value acts as a global fallback when no context site is available. A site-specific Asset Base URI configured in the admin panel takes precedence over this value.
If assets.host is not configured and no context site can be determined, the site marked as General Domain is used as a last-resort fallback for its asset base URI.
See Site-based Host Configuration for the full resolution order and per-site asset base URI configuration.