Catch-all route for production build of Vue Router/Vite-based SPA - vue-router

I have a Vue3-based SPA using Vue Router and Vite as dev server. The app has three valid URL paths:
/
/first
/second
For production, it will be deployed on Apache under a prefix, i.e. the production URL paths will be:
/prefix
/prefix/first
/prefix/second
What I want to achieve is that clients should be redirected to a valid URL path (e.g. /prefix) of the application even when they initially request an invalid URL path, such as /prefix/invalid.
For this purpose, I've defined the following router.js:
import { createWebHistory, createRouter } from "vue-router";
const routes = [
{
path: "/first",
component: () => import("./components/First.vue")
},
{
path: "/second",
component: () => import("./components/Second.vue")
},
{
path: "/:anything+",
redirect: "/"
},
];
const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL), routes});
export default router;
The third route entry defines a catch-all route that will match whenever clients request an invalid URL path. This works fine in development on Vite, i.e. when serving the app via npm run dev -- --base='/prefix/'. Even when the browser initially requests /prefix/invalid, the app's /prefix route is loaded and displayed.
When deploying with npm run build -- --base='/prefix/' for production, however, the catch-all route starts working only after clients have initially requested /prefix or /prefix/index.html and the SPA has been loaded. When they initially request /prefix/invalid or even /prefix/first or /prefix/second, Apache responds with 404 Not Found. This is of course because the whole routing is implemented client-side in JavaScript, so my catch-all route and everything else routing-related will only work once the SPA itself has been loaded and its JavaScript is executing in the browser.
My question is: is there a way to make initial requests for invalid URL paths work in production on Apache like in dev on Vite? I tried adding a file public/.htaccess like this:
ErrorDocument 404 import.meta.env.BASE_URL
But the expression import.meta.env.BASE_URL, which Vite statically replaces with the base config option value in router.js, is not replaced in this file - which is consistent with the documentation of the public directory. Hence this approach doesn't work.
Not sure whether I'm following the right path with .htaccess or whether my business problem has any better -possibly simpler- solution?

The problem can be solved by generating .htaccess dynamically using a simple Vite plugin, which I define inline in vite.config.js for simplicity.
// vite.config.js
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
// A private Vite plugin for generating .htaccess dynamically
function generateHtaccess () {
// Resolved Vite configuration, including "base" option
let viteConfig;
return {
name: 'generate-htaccess',
// Rollup Plugin API output generation hook
// See https://rollupjs.org/guide/en/#generatebundle
generateBundle() {
this.emitFile({
type: 'asset',
fileName: '.htaccess',
source: `ErrorDocument 404 ${viteConfig.base}\n`
});
},
// Vite Plugin API specific hook
// See https://vitejs.dev/guide/api-plugin.html#configresolved
configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
},
};
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), generateHtaccess()],
});
A non-prefixed production build, such as npx vite build, will hence generate dist/.htaccess with the following content:
ErrorDocument 404 /
while specifying a prefix, e.g. npx vite build --base='/~user/' will generate:
ErrorDocument 404 /~user/
With this .htaccess, clients initially requesting any invalid URL path in production on Apache will be properly redirected to the same catch-all route -the app's root directory- as defined in router.js. This behavior will work consistently for any --base value supplied at build time.

Related

Svelte Kit Endpoint gives me 404

I made an Svelte Kit its working in my local with no problem but when i build it like this:
import adapter from '#sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
fallback: 'index.html',
})
}
};
And gives me 3 folders and they are: client, prerendered, server.
I'm uploading this 3 folders in my hosting and move the folder files into root folder. Everythings works with no problem BUT i have an api that sends mail. It's gives me 404? Send mail is working in localhost but not working in hosting. I can't fixed it. In manifest.json:
{
type: 'endpoint',
id: "api/sendMail",
pattern: /^\/api\/sendMail\/?$/,
names: [],
types: [],
load: () => import('./entries/endpoints/api/sendMail/_server.js')
},
The path is correct by the way.
The folders in hosting:
Photo
What can i do?
By specifying a fallback page, this means you're turning SPA mode on, so you can't use server endpoints.
From the adapter-static readme:
You can use adapter-static to create a single-page app or SPA by
specifying a fallback page.
The reason this is working local in dev:
During development, SvelteKit will still attempt to server-side render
your routes. This means accessing things that are only available in
the browser (such as the window object) will result in errors, even
though this would be valid in the output app. To align the behavior of
SvelteKit's dev mode with your SPA, you can add export const ssr =
false to your root +layout.

Static page within dynamic nested route not generated on NuxtJS

My goal is to have the following page structure
pages
--/users/
--/users/:id/
--/users/:id/edit/
edit should be generated for every :id. Right now my setup works only in development mode. On nuxt generate only the first /users/:id/edit/ is generated. All other routes get a 404 error on production!
Current setup:
- /users
- index.vue
/_id
- index.vue
- edit.vue
I also tried to add another _id.vue which i found in the documentation and here
/users
- _id.vue
- /id
[...]
I checked this as well.
In nuxt.config.js I have
ssr: true,
target: 'static',
Do I need to add something in router property in nuxt.config.js? I am not sure why it is not generated in production.
If you are using static site generation you have to define your static routes, because Nuxt does not know how many users do you have in your data base.
You can set a generate method into your nuxt.config.js file:
generate: {
fallback: true,
async routes() {
const routes = await _getRoutes()
return routes
}
}
And then declare a function called _getRoutes to generate a list of dynamic routes:
async function _getRoutes($content) {
const usersList = [1, 2, 3] // get from your DB
const paths = []
usersList.forEach((userId) => {
paths.push(`/users/${userId}`)
paths.push(`/users/${userId}/edit`)
})
return paths
}
this is a bug with nuxt 2 in vue 2.
for dynamically generated routes, and also not-english nested routes ,
(or maybe just generally non-english routes) when vue hydrates the component , it will face a problem and restarts it . so the data that you cached in data(){} with fetch(), will be empty again .
currently you should use static and/or english-only routes .

PWA doesn't load when router is in history mode (Vue CLI 3 based project)

I have a Vue CLI 3 based app which I would like to function as a PWA.
I need it to work in history mode, because the hash intervenes in redirects that I'm doing as part of an OAuth based authentication process.
When the routing is in hash mode, the manages to load fine as PWA. Once I change the mode to 'history', the PWA I installed won't load anymore - I'm getting a white screen instead. I checked the browser version and it still works fine (I configured the fallback in my NGINX server to index.html).
At first I used the default settings provided with vue CLI project and the PWA plugin. After doing some research, I added the following to my vue.config.js:
pwa: {
workboxOptions: {
skipWaiting: true,
navigateFallback: 'index.html'
}
}
and I saw that the following was indeed added to service-worker.js:
workbox.skipWaiting();
...
workbox.routing.registerNavigationRoute("index.html");
but it still didn't help. Even though the app registers successfully on my mobile device's homescreen, it just won't run unless I change the routing back to hash mode.
Does anyone have a clue what I might be missing?
Thanks
I have the same problem, i solved it by adding this in the end of my router.js
{
path: '*', // or '/index.html'
beforeEnter: (to, from, next) => {
next('/')
}
}
Try /index.html instead of index.html.

Vue Routes does not work properly in production

I am new to vuejs. For my Vuejs application, I cannot access url like '/foo/apple' in the web hosting server, after I run the "npm run build". It shows error 404 I am using the HTML5 History Mode,(https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations) and I implemented the "connect-history-api-fallback" like below in dev-server.js:
const express = require('express')
const app = express()
app.use(require('connect-history-api-fallback')())
My router/index.js
export default new Router({
mode: 'history',
{
path: '/Product',
name: 'Product',
component: Product,
},
{
path: '/Product/kraftbag',
name: 'Kraftbag',
component: Kraftbag
},
});
my website http://americandunnage.n55lwi.info/.
I have looked for a lot of posts regarding to this problem, but I still can find the solution.
you are likely missing the hashbang (#)
try #/foo/apple instead of /foo/apple
The default setting of the vue router is to use the hashbang method. This relies on using the index.html (or whatever defaults to / url) page, because everything after it is not taken as part of the url location but passed into the application. For example, in an anchor 1st heading this will go to a part of the page, and if you're already on that page, you will not be redirected.

VueJs 2: Unable to render view when using history mode and route params

I am trying to set up SPA routes using history mode as follows:
{
mode: 'history',
routes: [
{
path: '/',
component: Home
},
{
path: '/articles',
component: ArticleList,
children: [
{
path: ':title',
component: ArticleView
}
]
}
]
}
As I am using the history mode routing on vue and on express application I've set up "express-history-api-fallback" as the last middleware in the pipeline:
const app = express();
const root = path.join(__dirname, '../client/dist');
app.use(express.static(root));
/* other middlewares */
app.use(fallback('index.html', {root: root}));
At the moment of a page reload, everything works fine. I.e. loading a url http://application/articles, opens correctly the view, BUT when I try to access the view that takes in a parameter, no matter what, the view does not get loaded and two requests are made to the express.
I.E. a request to http://application/articles/test will resolve into two requests. One to http://application/articles/test and another one to http://application/articles/app.[calculated-hash].js
As far as I understand, the first request fetches the index.html the other request fetches the bundled js script.
Also, on the express app, all routes to api start with 'api' prefix.
Question:
What is wrong with my setup/code using history mode and route params because no routes with parameters are loaded when trying to access them when entering url manually or hitting refresh?
Update:
Using connect-history-api-fallback produces the same results
The problem was with the script tag the webpack injects into the index.html
<script type="text/javascript" src="app.06503cbc260646a996d9.js"></script>
The src attribute value was missing a '/' prefix thus the resolution of files failed.
So to make it work, I've linked the src file like this:
<script type="text/javascript" src="/app.06503cbc260646a996d9.js"></script>
As I am using the webpack to bundle js files, on the webpack config I've added output.publicPath '/'
More info: connect-history-api-fallback bugtracker