Vue-router redirect on page not found (404) - http-status-code-404

I'm trying to redirect to 404.html on page not found using the router.beforeEach global hook, without much success (using Vueand Vue-Router 1.0):
router.beforeEach(function (transition) {
if (transition.to.path === '/*') {
window.location.href = '/404.html'
} else {
transition.next()
}
});
This is not redirecting to page 404.html when a non-existing route is inserted, just giving me an empty fragment. Only when hard-coding some-site.com/* will it redirect to the expected some-site.com/404.html
I'm sure there's something very obvious here that I'm overlooking, but I cannot figure out what.
Please note that what I am looking for is a redirect to a new page, not a redirect to another route, which could be easily achieved by using router.redirect such as in these snippets:
router.redirect({
'*': '404'
})
Whereas on my router.map, I could have the following:
router.map({
'/404': {
name: '404'
component: {
template: '<p>Page Not Found</p>'
}
}
})

I think you should be able to use a default route handler and redirect from there to a page outside the app, as detailed below:
const ROUTER_INSTANCE = new VueRouter({
mode: "history",
routes: [
{ path: "/", component: HomeComponent },
// ... other routes ...
// and finally the default route, when none of the above matches:
{ path: "*", component: PageNotFound }
]
})
In the above PageNotFound component definition, you can specify the actual redirect, that will take you out of the app entirely:
Vue.component("page-not-found", {
template: "",
created: function() {
// Redirect outside the app using plain old javascript
window.location.href = "/my-new-404-page.html";
}
}
You may do it either on created hook as shown above, or mounted hook also.
Please note:
I have not verified the above. You need to build a production version of app, ensure that the above redirect happens. You cannot test this in vue-cli as it requires server side handling.
Usually in single page apps, server sends out the same index.html along with app scripts for all route requests, especially if you have set <base href="/">. This will fail for your /404-page.html unless your server treats it as a special case and serves the static page.
Let me know if it works!
Update for Vue 3 onward:
You'll need to replace the '*' path property with '/:pathMatch(.*)*' if you're using Vue 3 as the old catch-all path of '*' is no longer supported. The route would then look something like this:
{ path: '/:pathMatch(.*)*', component: PathNotFound },
See the docs for more info on this update.

#mani's response is now slightly outdated as using catch-all '*' routes is no longer supported when using Vue 3 onward. If this is no longer working for you, try replacing the old catch-all path with
{ path: '/:pathMatch(.*)*', component: PathNotFound },
Essentially, you should be able to replace the '*' path with '/:pathMatch(.*)*' and be good to go!
Reason: Vue Router doesn't use path-to-regexp anymore, instead it implements its own parsing system that allows route ranking and enables dynamic routing. Since we usually add one single catch-all route per project, there is no big benefit in supporting a special syntax for *.
(from https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes)

This answer may come a bit late but I have found an acceptable solution. My approach is a bit similar to #Mani one but I think mine is a bit more easy to understand.
Putting it into global hook and into the component itself are not ideal, global hook checks every request so you will need to write a lot of conditions to check if it should be 404 and window.location.href in the component creation is too late as the request has gone into the component already and then you take it out.
What I did is to have a dedicated url for 404 pages and have a path * that for everything that not found.
{ path: '/:locale/404', name: 'notFound', component: () => import('pages/Error404.vue') },
{ path: '/:locale/*',
beforeEnter (to) {
window.location = `/${to.params.locale}/404`
}
}
You can ignore the :locale as my site is using i18n so that I can make sure the 404 page is using the right language.
On the side note, I want to make sure my 404 page is returning httpheader 404 status code instead of 200 when page is not found. The solution above would just send you to a 404 page but you are still getting 200 status code.
To do this, I have my nginx rule to return 404 status code on location /:locale/404
server {
listen 80;
server_name localhost;
error_page 404 /index.html;
location ~ ^/.*/404$ {
root /usr/share/nginx/html;
internal;
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ #rewrites;
}
location #rewrites {
rewrite ^(.+)$ /index.html last;
}
location = /50x.html {
root /usr/share/nginx/html;
}
}

Do not use redirect; use beforeEnter
const router = [
// { path: '*', redirect: '/404'},
{ path: '*', beforeEnter: (to, from, next) => { next('/404') } },
{
path: '/404',
name: '404',
component: () => import('#/views/404')
}
]

I do not specifically answer your question, but I have an answer for anyone who is looking for page not found route syntax in Vue js just like I was looking for and came here.
Here are two things you can do:
First, create a component in the views and write some HTML like error 404!
Then import that component in index.js in router folder and import it there like:
import yourcomponentName from '../views/yourcomponentName.vue'
const routes = [
{
path: '/:catchAll(.*)', ->This is the regex pattern
name: 'whatever name you want to give',
component: yourComponentName
}]
If it does not work then use this
const routes = [
{
path: '/:pathMatch(.*)', ->This is the regex pattern
name: 'whatever name you want to give',
component: yourComponentName
}]
Remove the comment: 'This is the regex pattern it is writtern for understanding'

path: "*" is outdated. It belongs to vue-router2 on vue-router3. You can catch all 404 error pages by using
/:pathMatch(.*)*
It works well.

This will make it work too:
{
path: '/*/',
name: 'PageNotFound',
component: () => import('../views/PageNotFound.vue')
}

Related

Using nginx subdomains as a React parameter

As the title asks, is there a recommended method for using:
username.domain.com/event/id/
Where I can ask React to reference both id and username as parameters?
As of now
Currently I can obtain id fine using React router (or any parameter after the domain name in the url).
App.js
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
<Router>
<Routes>
<Route path="/event/:id/"
element={<>
<EventComponent />
</>}
/>
</Routes>
</Router>
EventComponent.jsx
import { useParams } from 'react-router-dom';
let params = useParams();
let eventID = params.id;
// I can now use eventID to reference what was in the url
I can catch server subdomains in my nginx.conf. This is what I use to reach my React app (not a wildcard subdomain yet, but that's not much of a change and not the problem):
server {
listen 443 ssl;
charset utf-8;
client_max_body_size 10M;
server_name domain.com;
root "/usr/share/nginx/html/domain.com/public_html";
location / {
index index.html;
try_files $uri $uri/ /index.html?/$request_uri;
}
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
}
Problem
But I don't know a clean way to pass subdomains to React without rewriting the url in nginx so that the subdomain is becomes part of the url after the domain. This is not a scenario that's desired.
(the following is an edit added to be more specific about the problem:)
I'm aware of getting the subdomain from window.location.hostname and splitting it to obtain a string, but I'm seeking clarity on whether this (or another method) is the most desired and won't have caveats that can be better avoided.
To illustrate my hestitation, I could also just get the id parameter from window.location, but I don't, as it appears to be typical to use useParams in react to do so. I'm always learning and, in doing so, try not to pick up bad habits.
Relevant criteria
As mentioned, I don't want to rewrite the domain from the above url into something like domain.com/event/id/username/
The structure of the url and presenting it as the original, to the user, is important.
Secondly, the subdomain is a wildcard. It won't be a fixed string. I can process this fine in nginx but it's important that a solution allows for a dynamic string.
A subdomain is something only the Webserver should handle.
React router is not able to distinguish between them.
There are multiple ways of achieving the desired outcome, I'll share my thoughts based on previous experiences.
Note: This assumes all the subdomains should point to the React index.html
Native + Props
Since all the subdomains will trigger the same React app, we can use the regular Javascript way of retrieving the subdomain, this can be passed to deeper components as a normal prop.
# App.js
const subdomain = // prefered way of retreiving subdomain;
<EventComponent subdomain={subdomain} />
# EventComponent
let params = useParams();
let eventID = params.id,
subdomn = props.subdomain
Query Parameter
(I've used this in production to move a header to a query parameter that is removed instantly so the user can't see it, works great)
You can let Nginx add a query parameter with the subdomain (../?user=foobar), this can easily be parsed with Javascript, to ensure the user does not notice this, you can remove the param after the page loads, the user won't even notice
server_name ~^(?<subdomain>.+)\.example\.com$;
return 301 http://example.com$request_uri?user=$subdomain;
Cookie
The same idea as above, but instead off using query parameters, you can inject the subdomain into a cookie and read it using Javascript
If username.domain.com is the public url, I don't quite see why you have to pass this to react, surely you can just interrogate the domain name from the browser directly.
If not, please add info with an example of the public and local (to nginx) domains and maybe elaborate on some of the reverse proxy rules and I'll remove the answer / attempt to update.
EventComponent.jsx
import { useParams } from 'react-router-dom'
const params = useParams()
const eventID = params.id
const username = window.location.hostname.split('.')[0]
Working example of using this method in React:
const Link = ReactRouterDOM.Link
const Route = ReactRouterDOM.Route
class ArticleComponent extends React.Component {
constructor(props) {
super(props)
this.hostname = window.location.hostname
this.username = window.location.hostname.split('.')[0]
}
render() {
return (
<div>
<p>Username fetched from parent App and passed as prop in the standard React manner</p>
<p><i>Note: It will say `stacksnippets` as the username, because there are only two levels in the hostname. If there were 3 levels eg, `user1.domain.com`, the username would be `user1`</i></p>
<p>Hostname: <b>{this.hostname}</b></p>
<p>Username via props: <b>{this.props.username}</b></p>
<p>Username from component directly: <b>{this.username}</b></p>
<p>Article ID: <b>{this.props.match.params.id}</b></p>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.props.username = window.location.hostname.split('.')[0]
}
render() {
return (
<ReactRouterDOM.HashRouter>
<ul>
<li><Link to="/">TO HOME</Link></li>
<li><Link to="/articles/link1">TO /articles/link1</Link></li>
<li><Link to="/articles/link2">TO /articles/link2</Link></li>
</ul>
<Route path="/" exact component={Home} />
<Route path="/articles/:id" render={(props) => (<ArticleComponent username={this.props.username} {...props}/>)} />
</ReactRouterDOM.HashRouter>
)
}
}
const Home = props => <div><h1>HOME</h1><p>Click a link to navigate</p></div>
ReactDOM.render(<App />, document.querySelector('#root'))
<script src='https://unpkg.com/react#16.3.1/umd/react.production.min.js'></script>
<script src='https://unpkg.com/react-dom#16.3.1/umd/react-dom.production.min.js'></script>
<script src='https://unpkg.com/react-router-dom#5.0.0/umd/react-router-dom.min.js'></script>
<script src='https://unpkg.com/babel-standalone#6.26.0/babel.js'></script>
<div id='root'></div>

Gatsby V4: How to implement gatsby-plugin-react-i18next for client only routes

I'm trying to implement client routes in Gatsby together with gatsby-plugin-react-i18next for two languages. I followed officel Gataby documentation and there is no client only path implementation explained.
Below is the code i implemeted.
gatsby-node.js
function langPrefix(page) {
return page.context.language === page.context.i18n.defaultLanguage &&
!page.context.i18n.generateDefaultLanguagePage
? ''
: `/${page.context.language}`
}
exports.onCreatePage = ({ page, actions }) => {
const { createPage } = actions
// Removing the ^ skips an optional /:lang prefix
if (page.path.match(/\/app/)) {
// adding lang if it's not the default page.
page.matchPath = `${langPrefix(page)}/app/*`
createPage(page)
}
}
Appjs
src/app/app.js
function App() {
return (
<>
<Router basepath="/:lang/app">
<PrivateRoute path="/accounthome" component={AccountHome} location=""/>
</Router>
<Router basepath="/app">
<PrivateRoute path="/accounthome" component={AccountHome} location=""/>
</Router>
</>)
}
export default App
Gatsby config
{
resolve: `gatsby-plugin-react-i18next`,
options: {
localeJsonSourceName: `locale`, // name given to `gatsby-source-filesystem` plugin.
languages: ["en", "fr"],
defaultLanguage: `en-us`,
fallbackLanguage: `en-us`,
// if you are using Helmet, you must include siteUrl, and make sure you add http:https
siteUrl: `https://my.costco.com/`,
ns: langTranslationConfig,
// you can pass any i18next options
i18nextOptions: {
interpolation: {
escapeValue: false // not needed for react as it escapes by default
},
nsSeparator: false
},
pages: [
{
matchPath: '/:lang/app/accounthome',
getLanguageFromPath: true,
excludeLanguages: ['en-ca']
},
{
matchPath: '/preview',
languages: ['en']
}
]
}
}
Router path : http://localhost:8000/en-us/app/accounthome
When am accessing this rote in browser This code show Gatsby.js development 404 page not found. Any pointer what am missing and am not sure how to access the translation contents in client only route page example account home page. Do i need to write the graph query in account home page or i dont need to ?
I think your regex is leaking your code. I guess it should look like:
if (page.path.match(/^\/app/)) {
page.matchPath = `${langPrefix(page)}/app/*`
createPage(page)
}
Either way, check the created page list by accessing the 404 page in development mode. By default, there should be a list of all created pages. Check the paths there to see if you can spot any mistakes or trailing slash issues.
The problem here is that your page is not being generated properly, that's why it throws a 404, so checking the created pages may help you to spot the mistake or at least, a thread to pull.
After a few research I've seen that you are basing your approach in: Gatsby can't find client routes when using gatsby-plugin-react-i18next
In their case, the langPrefix function is only prefixing the language to the page slug if it's the default one:
function langPrefix(page) {
return page.context.language === page.context.i18n.defaultLanguage &&
!page.context.i18n.generateDefaultLanguagePage
? ''
: `/${page.context.language}`
}
In your case, I'm not sure the plugin supports en-us (it's en-US according to https://github.com/microapps/gatsby-plugin-react-i18next/issues/100) and I think that's the reason why there's a leak in your page creation. Try using en instead of en-us or directly looking for en-US paths.

Why does react-router not works at vercel?

I am trying to publish a serverless web to vercel.
I want to use react-router and this works good on my computer but when I deploy it It doesn't works
Can somebody help me?
(I want to do it without server)
// My main code
import React from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import Main from './routes/Main'
import Tos from './routes/Tos'
import Privacy from './routes/Privacy'
import NotFound from './routes/NotFound'
import Recruit from './routes/Recruit'
const App = () => {
return (
<BrowserRouter>
<Switch>
<Route exact path = '/' component = {Main} />
<Route exact path = '/recruit' component = {Recruit} />
<Route exact path = '/tos' component = {Tos} />
<Route exact path = '/privacy' component = {Privacy} />
<Route component = {NotFound} />
</Switch>
</BrowserRouter>
)
}
export default App
Add a vercel.json file at the root of your project, and use "rewrites" to rewrite all incoming paths to refer to your index path.
For example:
{
"rewrites": [
{"source": "/(.*)", "destination": "/"}
]
}
Check here for further information: https://vercel.com/docs/configuration#project/rewrites
Specifying each Route
In order to make the React Router work in Vercel I had to specify each route in the vercel.json file, as mentioned in Surbina's answer. (Thanks btw, I used this solution for a quite some time)
{
"routes": [
{ "src": "/", "dest": "/" },
{ "src": "/recruit", "dest": "/" }
{ "src": "/tos", "dest": "/" }
// ....
]
}
But this may not be optimal depending on how many routes your application has, and honestly sometimes I forget to add new routes.
The "Match all" regex problem in Vercel
I tried the "match all" Regex as the source route [{ "src": "/*", "dest": "/" }], like I used to do in other hosting services, but for some Reason, Vercel server uses this routing for all requests, even when index.html needs to request a file like bundle.js, breaking the application.
Solution: Match routes but ignore files
And the solution I've found is using a Regex that matches all routes, except for routes that include a dot (.), which are usually files, like bundle.js. Then you can request a url like /recruit and it gets routed to / since it doesn't have a dot.
The regex is /[^.]+ (Turns into ^/[^.]+$ on Vercel).
Please note that this is a very simple Regex and may not cover all cases, however, I haven't got any issues with it to the moment of this comment.
So my vercel.json file looks like this:
{
"routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]
}
I hope it is useful for you!
Simply add this to your vercel.json, this will route all your routes to the index.html except for the js file
{
"routes": [
{
"src": "/[^.]+",
"dest": "/"
}
]
}
In vercel hosting service you can either deploy the whole project by selecting create-react-app in framework preset :
or you can deploy only the build folder after you've built the project by running npm run build and select Other as Framework preset :
Note
If you're using react-router-dom with BrowserRouter you should have vercel.json file at the root of your project folder or your build folder :
{
"rewrites": [
{
"source": "/((?!api/.*).*)",
"destination": "/index.html"
}
]
}
This configuration will prevent your web application to have 404 error when you refresh the page or when you open other routes .
Hope it helped you .
Maybe you need the server to redirect all requests to index.html. If Apache then you can use mod_rewrite and you would need some code like this:
RewriteCond %{REQUEST_URI} !^/index\.html
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/.*$ /index.html [L,PT]
It sounds like Vercel is looking in the wrong place for your files. Check the "homepage" field in your package.json; I've found that they're finicky, and sometimes what works when using localhost doesn't work on a Vercel deployment. If you don't have a `"homepage" field, start by adding
"name": "my-app",
"homepage": ".",
...
in package.json. That usually works for me, but if it doesn't, try the actual url of your deployment, i.e., https://something.vercel.app. I have had to do that for certain setups.
If that still doesn't work, check the Network tab in devtools and find your request, and check what url it's actually looking for resources with. That might give you some clues.
I'm using Create React App and react-scripts for building the project and I had an issue similar issue. The problem was the sub-routes content wasn't loading when I click browser refresh on the vercel's deployed site but locally it works fine.
ex. if you go directly to this sub route it would failed
https://mysite/top/second
It turns out that the problem is in my package.json I had "homepage": "." I simply fixed it by changing it "homepage": "" (removing the dot)
react-scripts build will read package.json homepage property and it will append the value from homepage to all the paths that links the js and css bundled code.
You can also run (your production build) like
yarn serve build -s
-s option redirects all not found paths to index.html, so you don't even need the serve.json/vercel.json file

How to force Gatsby to redirect a specific URL path to an external site?

I have a Gatsby site and due to some specific requirements, I need to redirect anyone who attempts to hit a specific URL path, for which there is no page, to an external site. This URL path is not a page within the site, but it's something that a user may be inclined to type due to documentation that is out of my control.
Here's an example: Let's say the site is located at https://www.example.com. A user may visit https://www.example.com/puppies, which does not exist. My file structure does not contain a src/pages/puppies.js file. However, when that URL is entered, I need to redirect the user to another site altogether, such as https://www.stackoverflow.com.
I haven't used Gatsby to that extent to know it has a configuration for this, so someone else may correct me. The way I would handle this is through the hosting provider where your app is.
For example, if you are using Netlify:
Create a _redirects file with the following content:
/* /index.html 200
Or
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
This will cause all https://yourwebsite.com/IDontHaveThisRoute to fallback to /index.html where your .js is loaded.
I provided the Netlify example only to give you the basic idea of how it can be done through the hosting provider of your choice. I would look into configurations I can put into redirects where my domain is deployed.
Thanks to Paul Scanlon he mentioned using onRouteUpdate in Gatsby and it works like a charm
import { navigate } from 'gatsby';
export const onRouteUpdate = ({ location }) => {
if (location.pathname === '/dashboard') {
navigate('/dashboard/reports');
}
};
This question helped point me in the right direction. I was able to get it to work using Gatsby's componentDidMount() to force a redirect as shown below, using a new file called puppies.js to "catch" the path typed by the user:
// puppies.js
import React, { Component } from 'react'
class Puppies extends Component {
componentDidMount() {
window.location.replace("https://www.stackoverflow.com");
}
render() {
return <div />
}
}
export default Puppies

404 for all routes with included locale - NextJS with next-i18next

Hi I am following the docs from here: github docs
, but all URLs with locales return 404.
For the example:
localhost:3000/de/second-page = 404
localhost:3000/en/second-page = 404
localhost:3000/de = 404
localhost:3000/en = 404
same for the default locale.
localhost:3000/second-page = 200
localhost:3000 = 200
I guess, this is because there is no pages/de/second-page.js, for the example, but what's the purpose then of this library, if it doesn't solve this problem.
I have searched a lot and I know about localSubpaths, but seems that it doesn't work:
module.exports = new NextI18Next({
otherLanguages: ['fr'],
//localeSubpaths: localeSubpathVariations[localeSubpaths],
localeSubpaths: {
de: 'de',
en: 'en',
}
})
It doesn't work in my app, but it doesn't work in their sample, too: their sample
Have you checked these two steps ?
After creating and exporting your NextI18Next instance, you need to
take the following steps to get things working:
Create an _app.js file inside your pages directory, and wrap it with
the NextI18Next.appWithTranslation higher order component (HOC). You
can see this approach in the examples/simple/pages/_app.js. Your app
component must either extend App if it's a class component or define a
getInitialProps if it's a functional component (explanation here).
Create a next.config.js file inside your root directory if you want to
use locale subpaths. You can see this approach in the
examples/simple/next.config.js (Next.js 9.5+ required).
const { nextI18NextRewrites } = require('next-i18next/rewrites')
const localeSubpaths = {
de: 'de',
en: 'en',
};
module.exports = {
rewrites: async () => nextI18NextRewrites(localeSubpaths),
publicRuntimeConfig: {
localeSubpaths,
},
}

Resources