Plugins

Breadcrumbs

Contribute one localized crumb per plugin route and let VitNode assemble the trail for public pages and AdminCP screens.

VitNode renders a breadcrumb trail in both the AdminCP header and the public site layout. Every matched route contributes one crumb, parent to child:

Home / Catalog / Products / Laptops / MacBook Pro

So a route says what it is called and nothing else. VitNode owns the separators, the nav and aria-current semantics, and the locale-aware link to each route's own URL—a plugin never builds a router link, and never restates the crumbs of the layouts above it.

Quick start

A static, translated crumb

Declare a component on definePluginRoute. It renders inside the message namespaces the route declared, so useTranslations just works:

plugins/catalog/src/pages/products-layout.tsx
import { definePluginRoute } from '@vitnode/core/routing'
import { useTranslations } from 'use-intl'

function ProductsBreadcrumb() {
  const t = useTranslations('@acme/catalog')

  return t('breadcrumbs.products')
}

export const route = definePluginRoute({
  breadcrumb: ProductsBreadcrumb,
})

A crumb read from the loader

The crumb is handed its own match's data, so a dynamic route can name itself with what it fetched—no second request, and no guessing from the URL:

plugins/catalog/src/pages/category-layout.tsx
import type { PluginRouteBreadcrumbProps } from '@vitnode/core/routing'
import { definePluginRoute } from '@vitnode/core/routing'

interface Category {
  name: string
}

function CategoryBreadcrumb({
  loaderData,
}: PluginRouteBreadcrumbProps<Category>) {
  return loaderData.name
}

export const route = definePluginRoute({
  load: async ({ params }) => await fetchCategory(params.categorySlug),
  breadcrumb: CategoryBreadcrumb,
})

PluginRouteBreadcrumbProps<TData, TSearch> carries loaderData, params and search—the same three names the loader, head and the page component receive.

Leaving a route out

A route that declares no breadcrumb contributes nothing, and its parents' crumbs stay exactly where they were. false says the same thing on purpose, which is worth doing when a page's frame already names the screen:

plugins/catalog/src/pages/products-index-page.tsx
export const route = definePluginRoute({
  breadcrumb: false, 
})

Rules

DeclarationWhat the trail does
A componentOne crumb, given this route's loader data and params
falseThis route is left out; its parents' crumbs remain
Nothing at allThe same, said by omission
The last crumbRendered as the current page, not as a link
Every other crumbA locale-aware link to that route's own URL

AdminCP labels come from the navigation

An AdminCP screen's trail is named by the sidebar this administrator can actually see, so a plugin that adds a nav entry gets its label for free—in every language. Keep the route, the sidebar entry and the translations in the same package.

Return a label, not a trail

A crumb returns text or an element. Do not render a <Breadcrumb>, a separator, or a link: the shell draws the trail above the page outlet and needs each crumb as one item so it can put them in one navigation landmark.

Learn More