Imported from veliovgroup/Meteor-flow-router-meta (
AGENTS.md). Install upstream withnpx skills add veliovgroup/Meteor-flow-router-meta. Copyright stays with the author.
Agent notes: ostrio:flow-router-meta
Use when editing this package, wiring head tags in Meteor apps with ostrio:flow-router-extra, or debugging <head> / SEO / JSON-LD on the client. Upstream canonical repo: Meteor-flow-router-meta. Companion router doc: ostrio:flow-router-extra (flow-router repo AGENTS.md).
Package identity
| Item | Detail |
|---|---|
| Atmosphere | ostrio:flow-router-meta — Atmosphere |
| Version | Package.describe in package.js (keep README compatibility in sync when releasing) |
| Arch | Client-only — api.mainModule('flow-router-meta.js', 'client'); no server entry |
| Role | Syncs <head> meta, link, script from Flow Router route / group / globals options |
| Implies | ostrio:flow-router-title@3.5.0 — re-exports FlowRouterTitle from meteor/ostrio:flow-router-meta so apps can use one import line |
Peer expectations (apps)
- Router:
ostrio:flow-router-extra@3.13.0+(constructor takes sameFlowRouter/Routerinstance you use for routes). - Typings: optional
zodern:types+ apptypescript(weak in package); publishedindex.d.tsis a client asset (api.addAssets('index.d.ts', 'client')). FlowRouterMeta/FlowRouterTitle: construct from client code after routes exist (same asmainModule).
package.js surface
| Item | Detail |
|---|---|
| Meteor | api.versionsFrom(['1.4', '2.8.0', '3.0.1', '3.4']) |
| Use | ecmascript, ostrio:flow-router-title@3.5.0 on client |
| Weak | zodern:types@1.0.13, typescript on client |
| Tests | Package.onTest: tinytest, jquery, random, ostrio:flow-router-extra@3.13.0, ostrio:flow-router-title@3.5.0, tests.js on client |
Repo / file layout (upstream mirror)
| Path | Role |
|---|---|
flow-router-meta.js |
FlowRouterMeta class, exports FlowRouterTitle re-export |
index.d.ts |
FlowRouterMeta(router: Router) + FlowRouterTitle re-export (Router from meteor/ostrio:flow-router-extra) |
tests.js |
Tinytest: globals, routes, null unset, nested groups, application/ld+json, FlowRouter.notFound vs catch-all * |
README.md |
User-facing API, examples, meteor test-packages instructions |
Public API
import { FlowRouterMeta, FlowRouterTitle } from 'meteor/ostrio:flow-router-meta';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
// After all FlowRouter.route / group definitions:
new FlowRouterMeta(FlowRouter);
new FlowRouterTitle(FlowRouter);
new FlowRouterMeta(router)— registersrouter.triggers.enterwithmetaHandler; wrapsrouter._notfoundRouteso 404 / not-found still runs head sync.- Does not monkey-patch
FlowRouter.route/group— readscontext.route.options,context.route.group,router.globalsat enter time.
Allowed option keys (same shapes on FlowRouter.route, FlowRouter.group, objects in FlowRouter.globals.push({...})):
meta,link,script— each: plain object,(params, queryParams, data) => object, or (for leaf values) nested functions resolved by_getValue.- Per-key values: string shorthand, attribute object, or functions;
nullor empty resolved object removes that logical key’s DOM node (seetests.js“Unset via null”).
How it attaches to the router
triggers.enter—metaHandler(context, _redirect, _stop, data)receivesdatafrom the routedata()hook (same idea as title package). Fourth argument is what Flow Router passes into enter triggers after data resolves._notfoundRoutewrap — copiesmeta/link/scriptfromFlowRouter.notFoundorFlowRouter.notfound(object oroptionssub-object) into a syntheticcontext.route.options, then callsmetaHandleron asetTimeout(..., 5)(with or without mergingrouter._current). Supports both catch-allFlowRouter.route('*', …)and legacyFlowRouter.notFound = { … }(see README “404 / notFound compatibility”).- Debounce —
metaHandlerclears/restarts a 5ms timer so rapid navigations coalesce to one DOM pass.
Merge and resolution order (implementation)
Source: _setTags in flow-router-meta.js.
FlowRouter.globals— array iterated from last index down to0. Each object’smeta/link/scriptis merged withObject.assign({}, previous, resolved), so earlierglobals.push()wins over later pushes for the same logical key.- Group —
_fromParent(context.route.group, tagType, …): walksgroup.parentchain. If a group’soptionshas the tag key (meta,link, orscript), that branch returns immediately withObject.assign({}, resolvedGroupValue, result)(defaultresult{}). So the innermost group that defines that tag type supplies the group contribution for that tag family — not a deep merge of every ancestor’s separatemetaobjects. Nested routes that only override part of SEO often rely on route-levelmeta, globals, or repeating keys on the child group. - Route —
context.route.options[tagType]merged last (route wins over globals + group for same logical keys).
Logical names — object keys under meta / link / script (e.g. description, canonical, ldjson). DOM nodes get data-name="<key>" for idempotent updates.
Stale keys — the update loop only iterates keys present in the merged elements[tagType] object. Logical keys absent from the merged result are not automatically removed on navigation; use null (or empty resolved value) for a logical name to remove its tag. See tests.js for explicit unsets.
Attribute shorthand (_getAttrs)
| Tag | String value for key K |
Object value |
|---|---|---|
meta |
name="K", content="<string>" |
Defaults name: K, then spreads your attrs (override name / content as needed) |
link |
rel="K", href="<string>" |
Defaults rel: K, then spreads attrs |
script |
src="<string>" |
Object used as-is (no default rel/name) |
innerHTML — special-cased: sets element.innerHTML (used for type: 'application/ld+json' + JSON string). Other attrs use setAttribute.
Cleanup — after applying attrs, attributes not in the resolved object (except data-name) are removed from the element.
Types — only string attribute values are applied (non-strings skipped in the attr loop).
_getValue (functions & nesting)
- Functions receive
_arguments=[context.params, context.queryParams, data];thisinside handlers is_context(merged fromcontextwithqueryalias forqueryParams). - Resolves nested functions on objects/arrays recursively.
nullpropagates; used to drop a keyed tag.
Practical constraints (tell users / agents)
- Client-only — no SSR head injection from this package; for SSR SEO use Fast Render / prerender / server head separately.
- CSS/JS
link/script— tags can be removed from DOM on route change, but loaded scripts and styles stay in memory; README warns you cannot fully “unload” global side effects. - Initialization order — define routes (including
*404 if used) and globals, thennew FlowRouterMeta/new FlowRouterTitle(typically end of client router module).
TypeScript
- Import
Routertype frommeteor/ostrio:flow-router-extrain the app soFlowRouterMetaconstructor resolves. - Package
index.d.tsdocuments client-only behavior.
Running tests (maintainers)
From package root (versions must satisfy package.onTest constraints):
meteor test-packages ./
# meteor test-packages ./ --port 8888
Uses Tinytest + tests.js (jQuery selectors on document).
Type-only assertions (tsd): npm install once, then npm run test:tsd (checks index.d.ts vs index.test-d.ts; stubs under tsd-stubs/ replace Meteor meteor/* modules).
Related
ostrio:flow-router-title— document title; implied and re-exported here.ostrio:flow-router-extra— routing,globals,data(), triggers, not-found APIs.