# \[Aurelia 2\] Help Needed: finalInstruction.children is empty at au:router:navigation-end

**URL:** <https://discourse.aurelia.io/t/aurelia-2-help-needed-finalinstruction-children-is-empty-at-aunavigation-end/6366>\
**Category:** Help Requests\
**Created:** [March 6, 2025, 10:12am UTC](https://discourse.aurelia.io/t/aurelia-2-help-needed-finalinstruction-children-is-empty-at-aunavigation-end/6366 "2025-03-06T10:12:36Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![graycrow](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/graycrow/32/323_2.png) [@graycrow](https://discourse.aurelia.io/u/graycrow)\
**Post date:** [March 6, 2025, 10:12am UTC](https://discourse.aurelia.io/t/aurelia-2-help-needed-finalinstruction-children-is-empty-at-aunavigation-end/6366/1 "2025-03-06T10:12:36Z")

</div>

I’ve been working through migrating my apps to Aurelia 2, and I’ve hit a just another roadblock that’s left me pretty stumped (and admittedly frustrated) after weeks of migrating. At the `au:router:navigation-end` event, I’m finding that the finalInstruction.children array is empty. From what I can tell, this is the only reliable way to access the current route details via `finalInstruction.children[0].recognizedRoute.route.endpoint.route.handler`, but without the children populated, I’m stuck.

I’ve been bouncing between dependency injection quirks and router issues for a while now, and I’d really appreciate any insights.

**Update:** Looks like something is wrong with my route config, and I need to take a closer look at the redirects.

---

<div class="post-metadata">

**Author:** ![mroeling](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/mroeling/32/781_2.png) [@mroeling](https://discourse.aurelia.io/u/mroeling)\
**Post date:** [March 6, 2025, 1:19pm UTC](https://discourse.aurelia.io/t/aurelia-2-help-needed-finalinstruction-children-is-empty-at-aunavigation-end/6366/2 "2025-03-06T13:19:00Z")

</div>

Greatly appreciated if you could share the cause and the solution if possible! It might help future development 🙂

---

<div class="post-metadata">

**Author:** ![graycrow](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/graycrow/32/323_2.png) [@graycrow](https://discourse.aurelia.io/u/graycrow)\
**Post date:** [March 6, 2025, 3:52pm UTC](https://discourse.aurelia.io/t/aurelia-2-help-needed-finalinstruction-children-is-empty-at-aunavigation-end/6366/3 "2025-03-06T15:52:22Z")

</div>

Well, it’s probably too specific for my app, but I’ll share it anyway. So, in Aurelia 1 I had the following route config, simplified:

```typescript
public configureRouter(config: RouterConfiguration, router: Router): void {
    const step: PipelineStep = {
        run: async (instruction: NavigationInstruction, next: Next): Promise<unknown> => {
            this.authorized = await this.session.getAuthorizationSatus();

            const result =
                (await this.checkAuthorization(instruction, next)) ||
                (await this.checkOrigin(instruction, next));

            if (result) {
                return result;
            }

            return next();
        },
    };

    config.addAuthorizeStep(step);
    config.options.pushState = true;
    config.options.hashChange = false;

    const routes: RouteConfig[] = [
        {
            route: ["", "/"],
            moduleId: PLATFORM.moduleName("./pages/login/login"),
            name: "login",
            nav: false,
            auth: false,
            title: "",
            activationStrategy: activationStrategy.replace,
        },
        {
            route: ["tickets"],
            moduleId: PLATFORM.moduleName("./pages/reports/reports"),
            name: "reports",
            nav: true,
            auth: true,
            title: "mmenu.reports",
            activationStrategy: activationStrategy.replace,
        },
        {
            route: "login",
            moduleId: PLATFORM.moduleName("./pages/login/login"),
            name: "login",
            nav: false,
            auth: false,
            title: "auth.login",
        },
        ...
    ];

    config.map(routes);

    router.ensureConfigured().then(() => {
        this.navTree = this.createNavTree(router.navigation);
    });
}

private async checkAuthorization(
    instruction: NavigationInstruction,
    next: Next
): Promise<unknown> {
    if (instruction.getAllInstructions().some((i) => i.config.auth)) {
        if (!this.authorized) {
            const currentUrl =
                instruction.fragment +
                (instruction.queryString ? `?${instruction.queryString}` : "");

            if (currentUrl && currentUrl !== "/") {
                setOrigin(currentUrl);
            }

            return next.cancel(new Redirect("login"));
        }
    } else if (
        this.authorized &&
        instruction.getAllInstructions().some((i) => i.fragment === "login")
    ) {
        return next.cancel(new Redirect("/tickets"));
    } else if (
        this.authorized &&
        instruction.getAllInstructions().some((i) => i.fragment === "/")
    ) {
        return next.cancel(new Redirect("tickets"));
    }
}

private async checkOrigin(instruction: NavigationInstruction, next: Next): Promise<unknown> {
    const origin = this.state.origin;

    if (origin && instruction.fragment !== "/login") {
        await clearOrigin();
        return next.cancel(new Redirect(origin));
    }
}

```

As you can see, the default route was set to login page and also authorize pipeline step was used to handle auth and navigate out of the login page when necessary. When it was converted to Aurelia 2, the auth step became the auth lifecycle hook, but the logic remained the same. Also, at this point I believe I was unsuccessful in creating the multiple routes with the same component, so the route config was also changed:

```typescript
routes: [
    {
        path: "", // Empty route
        redirectTo: "login", // Default to "login" if unauthorized; AuthorizeHook will override if authorized
    },
    {
        path: "login",
        component: Login,
        id: "login",
        nav: false,
        title: "login",
        data: { auth: false },
    },
    {
        path: "tickets/:id",
        component: Reports,
        id: "report",
        nav: false,
        title: "report.ticket",
        data: { auth: true },
        transitionPlan: RouterTransitionPlan.Replace,
    },
    ...
],

```

This resulted in an infinite loop of triggering “au:router:navigation-start” navigation events. In an attempt to investigate the problem, I first commented out the suspicious redirect to login from the empty route:

```typescript
  // {
  // path: "",
  // redirectTo: "login",
  // },

```

When that didn’t help after some debugging, I suddenly realized that I was still relying on the `fragment` property in my `checkAuthorization()` function, and that I had read somewhere that the concept of “fragment” had changed between the two versions, and that relying on it in the same way as in Aurelia 1 was probably the reason for my navigation loop.

In Aurelia 1 the router’s fragment property in navigation instructions represented the full path being navigated to, relative to the base URL. Aurelia 2’s router-lite fragment property has a more specific meaning: it represents the hash fragment of the URL (e.g., in /tickets#section1, fragment is “section1”), not the full path. The full route path is now accessed via `RouteNode.path`, and the matched route’s identifier is typically `RouteNode.instruction.recognizedRoute.route.endpoint.route.handler.id`(!).

My AuthorizeHook uses `this.fragment` to decide redirects:

```typescript
if (this.authorized) {
    if (this.fragment === "login" || this.fragment === "") {
        return "tickets";
    }
}

```

If fragment is “”, as in my case, because I’m not using hash (`useUrlFragmentHash: false`), this condition triggers a redirect to “tickets” even when the current path is already “tickets”. This creates a loop.

I fixed that, and even though I feel like it doesn’t pass my own quality control, it works:

```typescript
private checkAuthorization(next: RouteNode): string | null {
    const routeId =
        (next.instruction?.recognizedRoute?.route?.endpoint?.route?.handler as any)?.id ||
        next.path;

    // For auth-required routes, redirect to login if not authenticated
    if (next.data?.auth === true && !this.authorized) {
        const query = next.queryParams;
        const currentUrl =
            next.path + (Object.keys(query).length ? `?${new URLSearchParams(query)}` : "");

        if (currentUrl && currentUrl !== "/" && currentUrl !== "login") {
            this.actions.setOrigin(currentUrl);
        }

        return LOGIN_ROUTE_ID;
    }

    // For authenticated users, redirect from login or root to tickets
    if (this.authorized && routeId !== TICKETS_ROUTE_ID) {
        if (routeId === LOGIN_ROUTE_ID || routeId === "" || routeId === "/") {
            return TICKETS_ROUTE_ID;
        }
    }

    return null;
}

```

And this is where I got the empty children array from the original post that caused the problem of not being able to get route details. And the reason was simple: I had no children populated because **I had no default route!** In all the attempts to fix the loop, **I forgot to uncomment the default route redirect to the login page!** This one:

```typescript
{
    path: "",
    redirectTo: "login",
},

```

When I put it back, the routing now works, until the next issue 🙂 But now some custom elements are not rendering, but this has nothing to do with the routing.

As for router-lite, I found it complicated and confusing. There should be a better way to get current route details than going through a very deep structure of almost identical but different objects and guessing which one is which at what time. `currentTr` `.instruction` or`.finalInstruction`? `recognizedRoute`, `route`, `endpoint`, `route` (again!), `handler`? Some of this objects looks the same but parameters with the same names holds different entities, some types are missing, some are overlapping and confusing etc. There is a space for improvement.

---

<div class="post-metadata">

**Author:** ![mroeling](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/mroeling/32/781_2.png) [@mroeling](https://discourse.aurelia.io/u/mroeling)\
**Post date:** [March 6, 2025, 5:56pm UTC](https://discourse.aurelia.io/t/aurelia-2-help-needed-finalinstruction-children-is-empty-at-aunavigation-end/6366/4 "2025-03-06T17:56:43Z")

</div>

Thanks anyway! Appreciated!
