I'm trying to catch routing exceptions in my app. Basically there are 3 ways a user can navigate:
Typing the address - catching that is easy just { path: '**', redirectTo: 'errorPage'} in the route config.
The code called router.navigate(['pageName']) - catching that is easy too: .then(res => res, error => {
//service to log error to server
router.navigate(['errorPage']);
});
Clicking an HTML element that has [routerLink]="somePath" attribute.
It's the third one that I don't know how to catch.
Presumably such an error could occur if the module didn't load for some reason. I need to be able to handle that (and other unforeseen events) gracefully.
TO CLARIFY
The wildcard definition works for router problems, in most cases. But here's the thing, what if there is a route defined earlier in the configuration, but it leads to a module that can't be found! (I actually had this error in develop, which is why I am aware that it can happen, we're using lazy loading, and it happened that with some network problems a required module didn't load). The router finds a route, and tries to resolve it, but can't because the module, for whatever reason, did not load. The wildcard definition doesn't help here, because the router found a path but can't reach it. So it throws an error, which I'm trying to catch. Because for some reason router errors choke the whole app.
Why not create fallback path in routing module?
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: '**', component: NotFoundComponent },
];
When user enter not existing url will be redirected to NotFoundComponent.
This { path: '**', redirectTo: 'errorPage'} is what is called the wildcard route definition, which Angular official doc defines as:
The router will select this route if the requested URL doesn't match any paths for routes defined earlier in the configuration. This is useful for displaying a "404 - Not Found" page or redirecting to another route.
So this should work for any case when the requested route doesn't match any of the defined routes (doesn't matter if it was typed or set). Check if the routerLink definition is correct because the definition for it is without the brackets: <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
Related
We have a vanilla Vue/Vite setup and I'm receiving TypeError: Failed to fetch dynamically imported module on sentry logs.
It seems like the errors are correlated in time with new deployment to prod, although I don't have enough data to confirm. It doesn't happen on local and appears only on deployed code.
I've seen some similar questions for react's setups, but none with a satisfactory response.
I've also found a similar question regarding dynamically imported svgs, but our errors happen for full components.
The only place where we use dynamic imported components is on routing:
export const router = createRouter({
history: routerHistory,
strict: true,
routes: [
{
path: '/',
name: routes.homepage.name,
component: () => import('#/views/Home.vue'),
children: [
{
path: '/overview',
name: routes.overview.name,
component: () => import('#/views/Overview.vue'),
},
// other similar routes
],
},
],
});
Our deps versions:
"vue": "^3.0.9",
"vue-router": "^4.0.5",
"vite": "^2.0.5",
Any additional information on this issue and how to debug it would be much appreciated!
When you dynamically import a route/component, during build it creates a separate chunk. By default, chunk filenames are hashed according to their content – Overview.abc123.js. If you don't change the component code, the hash remains the same. If the component code changes, the hash changes too - Overview.32ab1c.js. This is great for caching.
Now this is what happens when you get this error:
You deploy the application
Your Home chunk has a link to /overview route, which would load Overview.abc123.js
Client visits your site
You make changes in your code, not necessarily to the Overview component itself, but maybe to some children components that Overview imports.
You deploy changes, and Overview is built with a different hash now - Overview.32ab1c.js
Client clicks on /overview link - gets the Failed to fetch dynamically imported module error, because Overview.abc123.js no longer exists
That is why the errors correlate with deployments. One way to fix it is to not use lazy loaded routes, but that's not a great solution when you have many heavy routes - it will make your main bundle large
In my case the error was caused by not adding .vue extension to module name.
import MyComponent from 'components/MyComponent'
It worked in webpack setup, but with Vite file extension is required:
import MyComponent from 'components/MyComponent.vue'
I had the exact same issue. In my case some routes worked and some didn't. The solution was relatively easy. I just restarted the dev server.
The accepted answer correctly explains when this error is triggered but does not really provide a good solution.
The way I fixed this is by using an error handler on the router. This error handler makes sure that when this error occurs (so thus when a new version of the app is deployed), the next route change triggers a hard reload of the page instead of dynamically loading the modules. The code looks like this:
router.onError((error, to) => {
if (error.message.includes('Failed to fetch dynamically imported module')) {
window.location = to.fullPath
}
})
Where router is your vue-router instance.
My situation was similar.
I found that my Quasar setup works fine on the initial page but not page that are loaded dynamically through an import('../pages/page.vue');.
Short response:
I replaced import('../pages/TestPage.vue') in the middle of the route file by import TestPage from '../pages/TestPage.vue' at the top.
More detailed response:
In my situation I don't expect to have much pages, a single bundle with no dynamic loading is fine with me.
The solution is to import statically every page I need.
In my routes.ts I import all the pages I need.
import IndexPage from '../pages/IndexPage.vue';
import TestPage from '../pages/TestPage.vue';
Then I serve them statically in my routes :
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: 'test', component: () => TestPage },
{ path: '', component: () => IndexPage }
],
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue'),
},
];
I recently expriencied this. The error was caused by an empty href inside an a tag: <a href="" #click="goToRoute">. You can either remove the href or change the a tag to something else, ie. button. Let me know if this helps.
I had the same problem. I found that I had not started my project.
Say I have 2 routes '/users' and /users/:id. First one renders UserListComponent and second UserViewComponent.
I want to re-render component when navigating from /users/1 to /users/2. And of course if I navigate from /users to /users/1 and vice versa.
But I DON'T want to re-render component if I navigate from /users/1?tab=contacts to /users/1?tab=accounts.
Is there a way to configure router like this for entire application?
--
Update:
I'm importing RouterModule in AppRoutingModule like this:
RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })
I'm using Angular 12
The default behavior of the Angular router is to preserve the current component when the URL matches the current route.
This behavior can be changed by using the onSameUrlNavigation option:
Define what the router should do if it receives a navigation request
to the current URL. Default is ignore, which causes the router ignores
the navigation. This can disable features such as a "refresh" button.
Use this option to configure the behavior when navigating to the
current URL. Default is 'ignore'.
Unfortunately, this option is not fine-grained enough to allow reload for path params and ignore for query params.
So you have to subscribe both to the query params and the path params changes with something like this:
constructor(route: ActivatedRoute) { }
ngOnInit() {
this.renderLogic();
this.route.params.subscribe(() => this.renderLogic());
this.route.queryParams.subscribe(() => this.renderLogic());
}
renderLogic() {
// ...
}
As far as I know, #Guerric P is correct, you can't completely re-render the component selectively like this, at least not without some trickery like subscribing to each event and then possibly blocking it for one scenario and not the other. Feel free to try something like that, but below is an alternative if you make use of resolvers to fetch your data.
What you can do is use runGuardsAndResolvers in your route configuration like so:
const routes = [{
path: 'team/:id',
component: Team,
children: [{
path: 'user/:name',
component: User
}],
runGuardsAndResolvers: 'pathParamsChange',
resolvers: {...},
canActivate: [...]
}]
This will, as the name suggests, run your guard resolver logic again. If you fetch data using resolvers and pass it into your components, you can update what your component displays only when the path or params change.
I have a file under the pages folder named about.tsx. So the path for the page is /about and I'm able to access the page by visiting example.com/about. However, if I visit example.com/About, it will redirect to a 404 page.
I've checked the Nextjs repo, seems like this is the expected behavior. Therefore, is there a workaround that can make the path case insensitive so that example.com/About will also work and direct users to the /about page?
With using next v12
There are a lot of similar questions here that has this answer already. I'd like to note that this answer is handling redirecting with the url parameters by adding this after the pathname:
${request.nextUrl.search}
Add a new file in /pages named _middleware.ts
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === request.nextUrl.pathname.toLocaleLowerCase())
return NextResponse.next();
return NextResponse.redirect(`${request.nextUrl.origin}${request.nextUrl.pathname.toLocaleLowerCase()}${request.nextUrl.search}`);
}
I agree that's Next.js's behavior, they only handle exact page name about instead of both about & About using the same file page/about.tsx
But the solution is you keep implementing a main page (e.g: about.tsx) and setup other pages to redirect to that page (e.g: About -> about) following this guide https://nextjs.org/docs/api-reference/next.config.js/redirects
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/About',
destination: '/about',
permanent: true,
},
]
},
}
// Set permanent:true for 301 redirect & clean SEO!
I have created a new sample app with aurelia-cli.
A weird behaviour that got me stuck is with routing.
This is my main route.
{
route: "projects",
title: 'Project Section',
name:'project-section',
moduleId: './modules/projects/project-section',
nav: true
}
and this is my project-section file
export class ProjectSection {
configureRouter(config, router) {
config.map([
{ route: '', moduleId: './projects-list', nav: false, title: 'Projects List' },
{ route: ':id', moduleId: './project-detail', nav: false, title: 'Project Detail' }
]);
this.router = router;
}
}
now when I open a link like http://myapp.com/projects, it works fine, and if I refresh the page, it still works.
If I click a link to open the detail page http://myapp.com/projects/9348 that also works fine. But on refreshing this detail page, browser goes blank with
GET http://localhost:9000/projects/scripts/vendor-bundle.js net::ERR_ABORTED
error message in console
Am I missing something obvious? Is there a config setting to enable the refreshing of pages with /:id like routes?
The code is here at github aurelia-sample and you clone and run as usual
au run --watch
Thanks for any help
EDIT: does it have anything to do with
config.options.pushState = true;
in the app config?
I don't know if your problem has been resolved. But Yes, it is to do with the pushState being set to true. I myself faced this issue before. It is something to do with how Aurelia loads the JS for the page. I was unable to resolve it (albeit I'll admit I looked only for about 20 mins). But from what I saw you need to configure some setting to make this work.
Ok. This will help http://aurelia.io/docs/routing/configuration#options.
Baiscally:
Add a <base href="http://localhost:9000"> to index.html to redirect loading content from the base url. The rest of the configuration can be as you have kept it.
Also add config.options.root = '/' in your router config
The point that you are missing is that, (IMHO kind of counterintuitively), the subroutes defined by ProjectsSection only come to life once you actually navigate to /projects.
In other words, the following sequence works:
navigate to projects-list component (route of '' relative to the route of the section, that is, projects/ relative to the app)
Refresh that same page
Navigate to a details page.
The reason this sequence works has to do with how Aurelia resolves routes.
It first tries to match your route against the root configuration. It sees that the url starts with projects. So it checks to which component projects leads. It is ProjectSection in your case, therefore it uses that and checks whether it finds a configureRouter method on it. It does, because you've defined one, so it invokes it.
From then on, the route that it will try to match against that configuration is the the original route without the prefix which lead to that component. It was projects/ which lead to ProjectSection, followed by nothing, so the remainder of the route is / which is resolved as per the configuration you've created inside ProjectSection. In your case, that leads to projects-list.
The important part about this is that by performing this sequence, Aurelia gets a chance to invoke the configureRouter method on ProjectSection (since at (2), it navigates once again to projects/ relative to the app root). Therefore, a configuration which can be used against the url will exist.
In the problematic case, if you immediately navigate to /projects/:id, it will be matched against the root level configuration. There is no match, and since reloading counts as a first page load, there is no route to fall back to. That's why you are getting the error.
The important part about this scenario is that, contrary to the former case, the router skips invoking the configureRouter method on ProjectSection. If you set a breakpoint in that method, you will see that it does not get invoked. That's why I commented on is behavior as being counter-intuitive, because the behavior you (and me as well, when I first faced this problem) expect is a fairly common scenario.
I don't know of any official way to resolve this, so my suggestion is that you could try defining a wildcard route on the app level config like so:
{
route: "projects/*",
title: 'Project Section',
name:'project-section',
moduleId: './modules/projects/project-section',
nav: true
}
and see if it works (I am not sure - I've tried to provide you with a reason, but I don't have the means to try to reproduce it at this very moment).
Try adding a redirect route in the ProjectSection configureRouter function like so (taking your example):
export class ProjectSection {
configureRouter(config, router) {
config.map([
{ route: '', redirect: 'list' }, // Redirects to the list route when no specific route in the project section is specified
{ route: 'list', moduleId: './projects-list', nav: false, title: 'Projects List' },
{ route: ':id', moduleId: './project-detail', nav: false, title: 'Project Detail' }
]);
this.router = router;
}
}
Thanks for all the answers posted. I finally resolved the issue. I am still not sure if this is the right way or not but here is the trick that works.
Taking hint from Rud156's and answer from this question How to remove # from URL in Aurelia I just added
<base href="/">
in the index.html file, and everything works as expected.
To further investigate the issue I cloned the aurelia sample contact app
1- when you run the app as it is after cloning, everything works great.
2- if you add
config.options.pushState = true;
in the src/app.js, child routes will stop working.
3- Add
<base href="/">
in the index.html and everything will start working.
I have the following routes variable defined in my app-routing.module.ts:
const routes: Routes =
[
{ path: '', redirectTo: '/users', pathMatch: 'full' },
{ path: 'users', component: UsersComponent },
{ path: 'dashboard', component: DashboardComponent }
];
With this current configuration, when I submit http://localhost:3000/users, the browser redirects to http://localhost:3000/users/users and then displays the user list binding in the html as expected.
However, something seems off kilter for the browser to redirect from /users to /users/users. If I remove the th first route config with the redirectTo attribute then the browser stays on /users without redirecting to /users/users. However, in this scenario, the user list binding doesn't display as expected.
Any idea what might be causing the redirect to /users/users? Any idea how I can keep the browser on /users and get the user list binding to properly display at this uri?
Option 1: Setting base tag
In order to get the router working properly a base href needs to be defined somehow for the app. The docs recommend adding a base element to the head of your index.html file, such as:
<base href="/">
Option 2: Setting a provider
This can be a bit dangerous however as it has (potentially unexpected) side effects on anchor tags, empty href tags, etc, etc. It also breaks inline svg sprites... which was a major part of our app's UI. If you want to make the router work but not break a lot of things you can actually define the base href elsewhere, like so:
// ... other imports
import { APP_BASE_HREF } from '#angular/common';
#NgModule({
// ... other pieces of ngModule
providers: [
{provide: APP_BASE_HREF, useValue : '/' }
],
// ... other pieces of ngModule
})
export class AppModule {
constructor() {}
}
As a basic example. It's a bit hard to find in the documentation but is a good workaround to get things going without messing with everything else.