As you might have seen in the 3.4 release blog post, Meteor has received a HUGE update with a lot of focus on the developer experience. One of the new additions was the new deferrable functions, which we will dive into today in this blog post.
Why were those functions added?
While seeing teams update to 3.4, we saw a recurring problem: build times have gotten faster with rspack, but the developers were not feeling it because they had a bottleneck at the startup.
With that in mind, we did our investigation, and we saw that those apps that were not feeling this increase in productivity were the ones that had async or costly operations being done at the startup. You can think of requesting data from external APIs, waiting for sidekick services to get started, etc.
For production, this makes sense — you don’t want your app to start without those dependencies ready. You might not need all that. With that in mind, we made this addition to our api.
New API
The added functions were: deferrable, deferDev, deferProd. I’ll start with the first.
⚠️ If a function is not deferred for the current environment, it runs immediately and behaves exactly like a normal function call (including return values).
Meteor.deferrable
In the docs, we can see it in action:
import { Meteor } from "meteor/meteor";
Meteor.startup(async () => {
await Meteor.deferrable(connectToExternalDB, {
on: ["development"],
});
});This means that, on development, this connectToExternalDB function will run deferred (scheduled to run after the initial startup completes, instead of blocking it).
You can define test, development, and production – for testing only environments, this can be very nice!
Conceptually, deferrable functions let Meteor finish booting the app first, and then run non-critical async work afterward. The app becomes usable sooner, even if some background initialization is still happening.
Meteor.deferDev
deferDev is an abstraction on top of deferrable with the options for development and testing environments.
This is probably the one that will be used the most. You should use this one for flagging those functions that can run later in development mode; Consistently using it will make your overall startup and rebuild faster.
In our hypothetical PR example, using deferDev was saving our startup time in around two seconds!
And to use it is very simple:
const connectToExternalExpensiveService = async () => {
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
console.log("Connecting to external expensive service...");
await sleep(Math.random() * 2000 + 1000); // simulate expensive connection
console.log("Connected to external expensive service!");
}
await Meteor.deferDev(connectToExternalExpensiveService);Meteor.deferProd
deferProd is an abstraction on top of deferrable with the options for production.
This is used for more one-off cases. We saw usage in this one for connecting to dev-only services and to overall services that will be used later in production, but were necessary to be there at startup when working locally.
A good example is warming a cache or connecting to an analytics pipeline that won’t be used during the first requests, but must still exist in production.
Using deferrable with values
The last notable thing about this new API is that deferred functions, if they are not deferred they, work exactly as if they were not marked as deferred.
You can see this logging example:
console.log("Before defer Dev");
Meteor.deferDev(async () => {
console.log("Connecting to some external service...");
console.log("This will run later on local mode!");
})
console.log("After defer Dev");
console.log("Before defer Prod");
Meteor.deferProd(async () => {
console.log("Connecting to some service...");
console.log("This will run later on prod mode!");
})
console.log("After defer Prod");
// in a regular meteor run
// Before defer Dev
// After defer Dev
// Before defer Prod
// Connecting to some service...
// This will run later on prod mode!
// After defer Prod
// Connecting to some external service...
// This will run later on local mode!Very cool, eh?
But also this API can return values so for example you can have this example in your code:
const value = Meteor.deferDev(() => "later_in_dev") || Meteor.deferProd(() => "later_in_prod");
console.log("value is set to:", value);If a function is not deferred for the current environment, it runs immediately and behaves exactly like a normal function call (including return values).
⚠️ You can see all the examples that I shared today in this PR
Migration & Conclusion
To start getting gains with this new API, begin by wrapping your slowest startup work — external connections, async setup, or non-critical initialization — with deferDev and measure the impact. Most teams will see improvements immediately.
Galaxy team saw these improvements first hand – their local environment, with these optimizations, got ~3x faster!
You can think of these functions as saying: this work is important — just not right now.
Treat deferDev as part of your startup hygiene: anything that doesn’t block correctness should be a candidate.
If you haven’t revisited your startup code since upgrading to 3.4, now is the perfect time — you may be sitting on seconds of free productivity without realizing it.
If you’re migrating to 3.4 and have questions, run into edge cases, or want to share how much startup time you shaved off using these APIs, join the conversation on the Meteor forums. We’d love to hear what worked for your app — and what didn’t.
We’re excited about what’s to come and can’t wait for you to join the Meteor renaissance!
For feedback, questions, or support, visit our forums or join our Discord channel.