apify/crawlee · error · Error

Default route is already defined! / Route for label '${Strin

Error message

Default route is already defined! / Route for label '${String(label)}' is already defined!

What it means

Router.validate (used by addHandler and addDefaultHandler) throws when a route for the given label already exists. The special label for the default route produces the 'Default route is already defined!' message; any other duplicate label produces 'Route for label X is already defined!'. Duplicate registration is treated as a programming error rather than an implicit override.

Source

Thrown at packages/core/src/router.ts:443

        if (schema) {
            context.request.userData = (await validateUserData(
                label!,
                schema,
                context.request.userData,
            )) as GetUserDataFromRequest<Context['request']>;
        }
    }

    /**
     * Throws when the label already exists in our registry.
     */
    private validate(label: string | symbol) {
        if (this.#routes.has(label)) {
            const message =
                label === defaultRoute
                    ? `Default route is already defined!`
                    : `Route for label '${String(label)}' is already defined!`;
            throw new Error(message);
        }
    }

    /**
     * Creates new router instance. This instance can then serve as a `requestHandler` of your crawler.
     *
     * ```ts
     * import { Router, CheerioCrawler, CheerioCrawlingContext } from 'crawlee';
     *
     * const router = Router.create<CheerioCrawlingContext>();
     * router.addHandler('label-a', async (ctx) => {
     *    ctx.log.info('...');
     * });
     * router.addDefaultHandler(async (ctx) => {
     *    ctx.log.info('...');
     * });
     *
     * const crawler = new CheerioCrawler({

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Register each label only once — guard router setup so it runs a single time
  2. If overriding is intended, recreate the Router or restructure so the new handler replaces the old at setup time
  3. Use distinct labels for distinct handlers
  4. Check for duplicate module initialization (hot reload, multiple entry points)

Example fix

// before
router.addHandler('detail', handlerA);
router.addHandler('detail', handlerB); // throws
// after
router.addHandler('detail', handlerA);
router.addHandler('list', handlerB);
Defensive patterns

Strategy: try-catch

Validate before calling

if (registered.has(label)) return; // skip duplicate registration

Try / catch

try {
  router.addHandler(label, handler);
} catch (err) {
  if (err.message.includes('already defined')) return; // idempotent setup
  throw err;
}

Prevention

When it happens

Trigger: Calling router.addHandler('products', h) twice, or addDefaultHandler(h) after a default handler was already registered.

Common situations: Module executed twice (double import, hot reload) registering routes again; initializing the router in a loop; two code paths both adding a default handler; copy-pasted registration blocks.

Related errors


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/431822579b40f768. Report an issue: GitHub.