{"record":{"id":"1ac8cb2d959f7987","repo":"remix-run/react-router","slug":"you-tried-to-define-routes-asynchronously-but-star","errorCode":null,"errorMessage":"You tried to define routes asynchronously but started defining routes before the async work was done. Please await all async data before calling `defineRoutes()`","messagePattern":"You tried to define routes asynchronously but started defining routes before the async work was done\\. Please await all async data before calling `defineRoutes\\(\\)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-router-remix-routes-option-adapter/defineRoutes.ts","lineNumber":72,"sourceCode":"}\n\n/**\n * A function for defining routes programmatically, instead of using the\n * filesystem convention.\n */\nexport const defineRoutes: DefineRoutesFunction = (callback) => {\n  let routes: RouteManifest = Object.create(null);\n  let parentRoutes: RouteManifestEntry[] = [];\n  let alreadyReturned = false;\n\n  let defineRoute: DefineRouteFunction = (\n    path,\n    file,\n    optionsOrChildren,\n    children,\n  ) => {\n    if (alreadyReturned) {\n      throw new Error(\n        \"You tried to define routes asynchronously but started defining \" +\n          \"routes before the async work was done. Please await all async \" +\n          \"data before calling `defineRoutes()`\",\n      );\n    }\n\n    let options: DefineRouteOptions;\n    if (typeof optionsOrChildren === \"function\") {\n      // route(path, file, children)\n      options = {};\n      children = optionsOrChildren;\n    } else {\n      // route(path, file, options, children)\n      // route(path, file, options)\n      options = optionsOrChildren || {};\n    }\n\n    let route: RouteManifestEntry = {","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/remix-run/react-router/blob/1fd704a7dabcbe3ae09d7387b460e6acaba30ec1/packages/react-router-remix-routes-option-adapter/defineRoutes.ts#L54-L90","documentation":"Thrown by defineRoutes() (remix-routes-option-adapter) when the route-defining callback calls the `route` helper AFTER defineRoutes has already returned. defineRoutes is synchronous: it invokes the callback, then sets alreadyReturned = true and returns the manifest. Any async work scheduled inside the callback that later calls route() will hit this guard because the manifest has already been handed back. The API requires all route definitions happen synchronously within the callback.","triggerScenarios":"Inside the routes() callback (the legacy remix `routes` option adapter), the developer awaits a fetch/FS read and then calls route() in a .then() / setTimeout / microtask, after defineRoutes returned. The alreadyReturned flag flips to true the moment the synchronous callback completes.","commonSituations":"Migrating a Remix v2 `routes` function that loaded route metadata asynchronously (e.g. from a CMS/DB) into the adapter. Forgetting that the legacy routes option is synchronous. Doing `await someAsync()` inside the callback and then continuing to call route().","solutions":["Do all async data fetching BEFORE calling defineRoutes — resolve the data, then pass it into a synchronous callback that only calls route().","If you need dynamic routes from an async source, fetch them at build time, write to a JSON file, and have the callback read that file synchronously (fs.readFileSync).","Re-check the @react-router/remix-routes-option-adapter docs: the callback must complete synchronously; restructure so all awaits happen upstream.","Switch to flatRoutes() or a static routes.ts if dynamic async routes aren't truly required."],"exampleFix":"// before — async work inside the callback fires after defineRoutes returns\nexport default (defineRoutes) => defineRoutes((route) => {\n  fetch('/api/routes').then((files) => files.forEach((f) => route(f.path, f.file))); // throws: alreadyReturned\n});\n// after — resolve async data first, then define synchronously\nconst files = await fetch('/api/routes').then((r) => r.json());\nexport default (defineRoutes) => defineRoutes((route) => {\n  files.forEach((f) => route(f.path, f.file));\n});","handlingStrategy":"validation","validationCode":"// ensure the callback passed to defineRoutes performs no await\nfunction isSyncCallback(fn: Function): boolean {\n  return fn.constructor.name !== 'AsyncFunction';\n}\nif (!isSyncCallback(routesCallback)) throw new Error('routes() callback must be synchronous');","typeGuard":"function isSynchronousRoutesCallback(fn: unknown): boolean {\n  return typeof fn === 'function' && fn.constructor.name !== 'AsyncFunction';\n}","tryCatchPattern":null,"preventionTips":["Resolve all dynamic route data BEFORE invoking defineRoutes.","Keep the routes() callback fully synchronous — only call route() in it.","If async is unavoidable, precompute into a JSON file and readFileSync inside the callback."],"tags":["routes","defineroutes","remix-adapter","async","config"],"backgroundTag":null,"analyzedSha":"1fd704a7dabcbe3ae09d7387b460e6acaba30ec1","analyzedAt":"2026-08-12T13:54:57.804Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}