A handled route is a fully-resolved route with all the metadata and configuration needed for Scully to render it into a static HTML file. Handled routes are created by router plugins that transform unhandled routes with parameters into concrete, renderable paths.
When Scully encounters an unhandled route with parameters, it passes the route through a router plugin. The plugin expands the parameterized route into one or more concrete routes.
Unhandled Route
Router Plugin
Handled Routes
// Discovered by route traversalconst unhandledRoute = '/user/:id';
This route has a parameter :id and cannot be rendered without knowing what values to use.
// Router plugin expands the routeasync function userRouterPlugin(route: string, config?: any) { const users = await fetchUsers(); return users.map(user => ({ route: `/user/${user.id}`, type: 'json', data: { user } }));}
The plugin fetches data and creates concrete routes.
All unhandled routes pass through a router plugin, even simple routes without parameters. Routes without a custom plugin configuration use the default router plugin, which converts them directly to handled routes.
The HandledRoute interface defines the structure of a handled route:
// From libs/scully/src/lib/routerPlugins/handledRoute.interface.tsexport interface HandledRoute { /** the string as used in the Scully config */ usedConfigRoute?: string; /** the _complete_ route */ route: string; /** the raw route, will be used by puppeteer over route.route */ rawRoute?: string; /** String, must be an existing plugin name. mandatory */ type?: string; /** the relevant part of the scully-config */ config?: RouteConfig; /** variables exposed to angular _while rendering only!_ */ exposeToPage?: { manualIdle?: boolean; transferState?: Serializable; [key: string]: Serializable; }; /** data will be injected into the static page */ injectToPage?: { [key: string]: Serializable; }; /** an array with render plugin names that will be executed */ postRenderers?: (string | symbol)[]; /** the path to the file for a content file */ templateFile?: string; /** optional title, if data holds a title, that will be used instead */ title?: string; /** additional data that will end up in scully.routes.json */ data?: RouteData; /** Plugin to use for rendering */ renderPlugin?: string | symbol;}
The RouteConfig interface provides additional configuration options:
export interface RouteConfig { /** this route does a manual Idle check */ manualIdleCheck?: boolean; /** type of the route */ type?: string; /** executed on render - return false to skip rendering */ preRenderer?: (route: HandledRoute) => Promise<unknown | false>; /** option to select a different render plugin */ renderPlugin?: string | symbol; /** Allow any other setting possible, depends on plugins */ [key: string]: any;}
For content files (Markdown, AsciiDoc, etc.), there’s an extended interface:
export interface ContentTextRoute extends HandledRoute { /** the type of content (MD/HTML) */ contentType?: string; /** The actual raw content that will be rendered */ content?: string | ((route?: HandledRoute) => string);}
Example:
const contentRoute: ContentTextRoute = { route: '/blog/my-post', type: 'contentFolder', templateFile: './blog/my-post.md', contentType: 'md', content: '# My Post\n\nThis is the content...', data: { title: 'My Post', author: 'Jane Doe' }};
Routes without parameters or custom configuration use the default router plugin:
// From libs/scully/src/lib/routerPlugins/defaultRouterPlugin.tsasync function defaultRouterPlugin(route: string) { return [{ route } as HandledRoute];}registerPlugin('router', 'default', defaultRouterPlugin);