Hello fellow developers!
I'm excited to share the latest updates to three essential Meteor.js libraries that I've been maintaining since 2015: ostrio:flow-router-extra
, ostrio:flow-router-title
, and ostrio:flow-router-meta
.
These packages streamline tasks related to managing routing, document titles, and meta-tags within Meteor applications. With the recent release of Meteor v3 and Blaze v3, I've updated these libraries to ensure compatibility and to introduce compatibility with modern asynchronous patterns, making them powerful tools for building scalable web applications today for tomorrow.
About FlowRouter and its Evolution
When I first ventured into Meteor.js development, I recognized a need for more flexible and feature-rich tools to handle routing and metadata management. The original flow-router
was a solid foundation, but I saw opportunities to extend its capabilities, leading to the creation of ostrio:flow-router-extra
. This package superseded original FlowRouter library as it wasn't updated for the past 9 years. ostrio:flow-router-extra
provides a more versatile routing solution, supporting multiple front-end frameworks including Blaze, Vue, React, Svelte, and beyond. Building upon this, ostrio:flow-router-title
and ostrio:flow-router-meta
were developed to offer seamless management of document.title
and meta-tags with support of reactive data sources.
What's New in ostrio:flow-router-extra@3.12.0
The latest release, version 3.12.0, brings several noteworthy enhancements:
-
Asynchronous Hooks:
action
,data
, andonNoData
hooks now support asynchronous functions, allowing for more efficient data fetching and rendering workflows -
Improved Compatibility: The package has been updated to ensure seamless integration with
meteor@3
and other modern packages, enhancing overall stability and performance - Enhanced TypeScript Support: TypeScript definitions have been refined, providing better type checking and developer experience
For a detailed overview, you can refer to the v3.12.0
release notes.
What's unique about FlowRouter and how is it used?
Here are my recent use cases and some development tasks that were solved using FlowRouter packages:
- Hybrid Front-Ends: Imagine project with React dashboards, Vue marketing pages, and Blaze for the rest and legacy pages. Since Flow-Router-Extra is framework-agnostic it allowed my team to keep routing logic inside FlowRouter and use three different frameworks within single application and project
-
E-Commerce Platform: Imagine a site with thousands of products. Using dynamic loading, we've cut loading times by loading product data and components only when a user visits a specific product page. The
data()
andwaitOn
hooks making this seamless, passing fetched data directly to templates. This also reducing bundle size and improving performance of the webapp - SEO Optimization: Paired with FlowRouterMeta and FlowRouterTitle, it ensures every route has tailored meta-tags and titles. For a blog platform we were recently working on, this boosted search rankings effortlessly
Integrating ostrio:flow-router-extra
with Various Front-End Frameworks
One of the strengths of ostrio:flow-router-extra
is its framework-agnostic design, enabling integration with Blaze, Vue, React, Svelte, and more. Read further to see examples of how to use FlowRouter with different frameworks.
ostrio:flow-router-extra
supports "exact code splitting" via dynamic data and component loading β allowing to load related components and data on per-route basis excluding those files from "app's bundle", this is great solution for performance and flexibility.
Blaze Integration
Since Blaze engine is part of Meteor ecosystem β FlowRouter shipped with built-in rendering and templating engine for Blaze. this.render()
method is available inside route's hooks.
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
FlowRouter.route('/', {
name: 'home',
action() {
// render directly to <body>
this.render('homeTemplate');
}
});
FlowRouter.route('/', {
name: 'home',
action() {
// render into {{> yield}} placeholder inside layout
this.render('layout', 'home');
}
});
Note:
kadira:blaze-layout
is outdated and wasn't updated in the past 10 years, it's advised to use built-inthis.render()
method to render Blaze templates.
To learn about all features related to Blaze-templating β read detailed "templating" tutorial.
Vue Integration
Create app
using new Vue()
or createApp
, then import and render Vue Components inside action()
hook
import Vue from 'vue';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
const app = new Vue({
el: '#app',
data: {
currentComponent: null
},
render(h) {
// Render the current component, or a fallback if it's not set
return this.currentComponent ? h(this.currentComponent) : h('div', 'Loading...');
}
});
FlowRouter.route('/', {
name: 'home',
async action() {
// Dynamically import the HomeComponent
const HomeComponent = await import('/imports/ui/HomeComponent.vue');
// Update the reactive property with the imported component.
app.currentComponent = HomeComponent.default || HomeComponent;
}
});
React Integration
Import and render React Components using react-dom
inside action()
hook
import React from 'react';
import { render } from 'react-dom';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
FlowRouter.route('/', {
name: 'home',
async action() {
const { default: HomeComponent } = await import('/imports/ui/HomeComponent.jsx');
render(<HomeComponent />, document.getElementById('app'));
}
});
Same can get achieved using mount
for better templating with App
as layout
import React from 'react';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { mount } from 'react-mounter';
import App from '/imports/ui/App.jsx';
FlowRouter.route('/', {
name: 'home',
async action() {
const { default: HomeComponent } = await import('/imports/ui/HomeComponent.jsx');
mount(App, {
content: <HomeComponent />
});
}
});
Svelte Integration
Import and render Svelte Components inside action()
hook
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
FlowRouter.route('/', {
name: 'home',
async action() {
const { default: HomeComponent } = await import('/imports/ui/HomeComponent.svelte');
new HomeComponent({
target: document.getElementById('app')
});
}
});
Key features examples
Read further to see how newly updated API can be used in day-to-day tasks.
Fetch data from Meteor.Method
We all know that pub/sub should be used only when live data updates are "must have" for everything else we use Meteor.callAsync
on the Client and Meteor.Methods
on the Server. Here's and example how to utilize it in FlowRouter route and data()
hook. Data returned from this hook is passed as third argument to all other hooks within the same route:
FlowRouter.route('/post/:_id', {
name: 'post',
async data(params) {
const post = await Meteor.callAsync('post.get', params._id);
if (!post) {
return;
}
return { post };
},
action(params, queryParams, { post }) {
this.render('postTemplate', { post });
},
title(params, queryParams, { post }) {
return post?.title || 'Post not found';
},
onNoData() {
this.render('page404');
}
});
Dynamic Module Loading with waitOn
Hook
The waitOn
hook in ostrio:flow-router-extra
facilitates dynamic loading of front-end code on a per-route basis, implementing the "exact-code-splitting" paradigm. This approach ensures that only the necessary code for a specific route is loaded, optimizing performance. Use whileWaiting
hook to render "Loading..." Component while data and modules are loading in waitOn
hook. For example:
import LoadingComponent from '/imports/ui/LoadingComponent.jsx';
const publicRoutes = FlowRouter.group({
title: "'Meteor App Title', // Default title for public routes"
whileWaiting() {
// Render Loading... spinner template between navigation
render(<LoadingComponent />, document.getElementById('app'));
}
});
publicRoutes.route('/about', {
name: 'about',
waitOn() {
return import('/imports/ui/AboutComponent.jsx');
},
async action() {
// import() will resolve instantly as it was cached after calling the same import inside `waitOn` hook
const { default: AboutComponent } = await import('/imports/ui/AboutComponent.jsx');
render(<AboutComponent />, document.getElementById('app'));
},
});
In this example, the AboutComponent
is dynamically imported only when the /about
route is accessed, reducing the initial bundle size and improving load times for users.
Asynchronous Data Fetching with data
Hook
The data
hook now supports asynchronous functions, allowing developers to fetch data before a route is rendered and pass it directly to the template or component. Value returned from data()
hook passed as third argument to all other hooks within the same route:
FlowRouter.route('/post/:_id', {
name: 'post',
waitOn(params) {
return Meteor.subscribe('post.get', params._id);
},
async data(params) {
const post = await PostsCollection.findOneAsync({ _id: params._id });
if (!post) {
return;
}
return { post };
},
action(params, queryParams, { post }) {
this.render('postTemplate', { post });
},
title(params, queryParams, { post }) {
return post?.title || 'Post not found';
},
onNoData() {
this.render('page404');
}
});
Here, the data
hook asynchronously retrieves a post by its _id
and passes it to the postTemplate
for rendering.
Use Cases and Problem Solving
Here are more examples how FlowRouter packages can improve solving day-to-day web development tasks
Managing Document Titles with ostrio:flow-router-title
Keeping the document title in sync with the current route enhances user experience and SEO. With ostrio:flow-router-title
, you can define global title and titles per route:
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { FlowRouterTitle } from 'meteor/ostrio:flow-router-title';
FlowRouter.globals.push({
title: 'My App Title'
});
FlowRouter.route('/about', {
name: 'about',
title: 'About Us',
action() {
this.render('aboutTemplate');
}
});
Build breadcrumbs in document.title
using titlePrefix
option in group
and nested Groups:
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { FlowRouterTitle } from 'meteor/ostrio:flow-router-title';
// My App > Profile
const profileRoutes = FlowRouter.group({
title: 'Profile'
titlePrefix: 'My App > '
});
profileRoutes.route('/profile', {
action() {
this.render('profile');
}
});
// My App > Profile > Settings
const settingsRoutes = profileRoutes.group({
title: 'Settings'
titlePrefix: 'Profile > '
});
settingsRoutes.route('/profile/settings', {
action() {
this.render('profileSettings');
}
});
Learn more about managing document.title
in ostrio:flow-router-title
package documentation
Dynamic Meta-Tags with ostrio:flow-router-meta
Meta-tags play a crucial role in SEO and social media sharing. With ostrio:flow-router-meta
, you can set meta-tags dynamically based on the route:
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { FlowRouterMeta } from 'meteor/ostrio:flow-router-meta';
FlowRouter.route('/product/:id', {
name: 'product',
async data(params) {
const product = await ProductsCollection.findOneAsync({ _id: params.id });
return { product };
},
meta(params, qs, { product }) {
return {
'description': product.description,
'og:image': product.imageUrl
};
},
action(params, queryParams, { product }) {
this.render('productTemplate', { product });
}
});
In this example, meta-tags such as the description and Open Graph image are dynamically set based on the product data, improving SEO and the appearance of shared links. Learn about all features of ostrio:flow-router-meta
package in its documentation. To ensure all meta-tags and page's content properly rendered when visited by search engines β use pre-rendering service.
Flow-Router-Extra and its companion packages are must-haves for Meteor.js developers. Officially recommended by the Meteor team, they offer unmatched flexibility, performance, and compatibility. Whether you're building an SPA, optimizing for SEO, or juggling multiple frameworks, these tools have you covered. Dive into the docs, try the examples, and let me know how they work for youβhappy coding!
Why Meteor.js
As we roll into 2025, Meteor.js remains a stellar choice for web development, and I'm here to tell you why from a developer's perspective. Its full-stack JavaScript platform continues to shine with its ability to deliver real-time, reactive applications out of the box β think chats, collaborative tools, or dashboards where data updates instantly across clients without breaking a sweat.
The latest Meteor 3.1 with Node.js 22 support, keep it cutting-edge, integrating seamlessly with modern front-end frameworks like React, Vue, Svelte, and our beloved Blaze
What makes it stand out is its simplicity: you write one language (JavaScript) for both client and server, slashing the cognitive load of juggling multiple tech stacks. Plus, features like the Distributed Data Protocol (DDP) and built-in reactivity mean you're not reinventing the wheel to sync data β something that's still a hassle in many other setups (I know and experienced it in the first hand). Add to that its zero-config development experience, TypeScript inference, and one-command build
Meteor.js is a tool that lets you focus on building features, not wrestling with boilerplate.
Meteor.js isn't just a framework β it's a full-blown development environment and a framework-agnostic build tool that's a no-brainer for both dev and production stages. The brightest minds at Meteor Software and the open-source community keep it thriving, pushing updates like the recent Express integration for RESTful APIs and maintaining a vibrant ecosystem of packages. It's not resting on its laurels from 2012; it's evolving with input from a dedicated Discord community and GitHub contributors.
Beyond Meteor itself, its DNA runs deep in famous open-source projects: Apollo GraphQL, born from the Meteor Development Group's pivot to a new backend layer, revolutionized data fetching, while Vulcan.js built a React/GraphQL stack atop Meteor's foundations. Even tools like InjectDetect trace their roots back to Meteor's real-time ethos. As a developer, I love that Meteor's build system handles transpilation, bundling, and hot module replacement without me touching a config file β whether I'm prototyping or shipping to production, it's a productivity booster that's hard to beat, even in 2025.
Top comments (7)
I've been using Meteor for almost 10 years now, and I've seen how much the community has grown and improved thanks to contributors like you, Dmitriy. Your packages have been instrumental in making my development process smoother and more efficient. Your contributions are a valuable asset to the community. Thank you for all that you do!
IMHO Meteor.js has one of the strongest communities, where the same contributors and maintainers dedicated to framework and its ecosystem for decades.
Iβm glad to be part of it, and happy when my packages help other developers to solve daily tasks and businesses to achieve their goals
Wow you covered all the integrations thats full service π₯
Thank you Jan π
Let me know if thereβs any other library that can play well with FlowRouter
Another great article, man! Keep them coming.
Thank you Gabs π
Trying to pick the pace now π π¨βπ»
Do you have better examples? Or another great story about Meteor.js? Please share in the comments