remix-run/react-router · error
You tried to define routes asynchronously but started…
Error message
You tried to define routes asynchronously but started defining routes before the async work was done. Please await all async data before calling `defineRoutes()`
What it means
In @react-router/remix-routes-option-adapter, defineRoutes(callback) invokes the callback synchronously to build the route manifest; the moment the callback returns, alreadyReturned is flipped. If route() is invoked afterwards — from a pending promise continuation, an await resumption, or a timer — the manifest is already frozen, so the adapter throws instead of silently dropping routes you defined late.
Solutions
- Do the async work in the enclosing async routes() function, then call defineRoutes with a fully synchronous callback
- Pre-fetch data before calling defineRoutes and close over the resolved values
- Audit the callback for any promise-returning calls and hoist them out
Example fix
// before — routes.ts
export default {
routes: () =>
defineRoutes(async () => {
const posts = await getPosts(); // callback returns before this resolves
for (const p of posts) route(p.slug, `routes/${p.slug}.tsx`);
}),
};
// after
export default {
routes: async () => {
const posts = await getPosts(); // await OUTSIDE defineRoutes
return defineRoutes(() => {
for (const p of posts) route(p.slug, `routes/${p.slug}.tsx`);
});
},
}; Defensive patterns
Strategy: validation
Validate before calling
// do async work BEFORE defineRoutes so the callback stays synchronous
export default {
routes: async () => {
const posts = await getPosts(); // await first
return defineRoutes(() => {
for (const p of posts) route(p.slug, `routes/${p.slug}.tsx`);
});
},
}; Prevention
- Keep the defineRoutes callback fully synchronous — no awaits, no .then, no timers
- Type the callback parameter as the sync DefineRoutesFunction so async callbacks fail to typecheck
- Fetch route metadata in the enclosing async routes() function
When it happens
Trigger: Passing a callback that starts async work and calls route() after it resolves (e.g. fetch(...).then(() => route(...)) or an awaited call inside a non-awaited async callback) — the callback itself returns before the async work finishes.
Common situations: Migrating Remix's routes option where route metadata is fetched from a CMS: doing the fetch inside the defineRoutes callback instead of before it; copy-pasting async setup into the callback.
Related errors
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/1ac8cb2d959f7987.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-router-remix-routes-option-adapter/defineRoutes.ts:72
}
/**
* A function for defining routes programmatically, instead of using the
* filesystem convention.
*/
export const defineRoutes: DefineRoutesFunction = (callback) => {
let routes: RouteManifest = Object.create(null);
let parentRoutes: RouteManifestEntry[] = [];
let alreadyReturned = false;
let defineRoute: DefineRouteFunction = (
path,
file,
optionsOrChildren,
children,
) => {
if (alreadyReturned) {
throw new Error(
"You tried to define routes asynchronously but started defining " +
"routes before the async work was done. Please await all async " +
"data before calling `defineRoutes()`",
);
}
let options: DefineRouteOptions;
if (typeof optionsOrChildren === "function") {
// route(path, file, children)
options = {};
children = optionsOrChildren;
} else {
// route(path, file, options, children)
// route(path, file, options)
options = optionsOrChildren || {};
}
let route: RouteManifestEntry = {View on GitHub (pinned to 6beaca3952)