Self Signed Cert error in Nuxt trying to generate static site locally - vue.js

I'm building a Vue/Nuxt (2) site running against a .NET Web Api. This site is already deployed in a staging capacity and is building and running as a statically generated site on Netlify. Now, I know it's not quite right as my content is not being rendered into the deployed files so effectively it's running as a SPA. Not quite what I saw happening in Dev at the time 5 weeks ago but I didn't think anything of it, I'd fix it later.
I've finally got a chance to work on this project again and proceeded to make the necessary changes so the content should be fetched via my dynamic route builder in nuxt.config.js (existing) and output during build via the asyncData hook in my pages (new).
Nuxt.config
// Generate dynamic page routes
let dynamicRoutes = async () => {
console.log( `${ process.env.API_BASE_URL }/page/main/generate` );
const fetchedConditions = await axios.get( `${ process.env.API_BASE_URL }/page/main/generate` );
const routesForConditions = fetchedConditions.data.map( ( condition ) => {
return {
route: `/conditions/${ condition.id }/${ condition.urlPath }`,
payload: condition
}
} );
console.log( `${ process.env.API_BASE_URL }/faq/top/generate?count=10` );
const fetchedFaqs = await axios.get( `${ process.env.API_BASE_URL }/faq/top/generate?count=10` );
const routesForFaqs = fetchedFaqs.data.map( ( faq ) => {
return {
route: `/frequently-asked-questions/${ faq.categoryId }/${ faq.id }/${ faq.urlPath }`,
payload: faq
}
} );
const routes = [ ...routesForConditions, ...routesForFaqs ];
return routes;
}
export default {
target: 'static',
ssr: false,
generate: {
crawler: true,
routes: dynamicRoutes
},
server: {
port: 3001
}...
Condition page
async asyncData(ctx) {
util.debug('Async data call...');
if (ctx.payload) {
ctx.store.dispatch("pages/storePage", ctx.payload);
return { condition: ctx.payload };
} else {
const pageResponse = await ctx.store.dispatch('pages/getCurrentPage', { pageId: ctx.route.params.id });
return { condition: pageResponse };
}
}
So far so good except now, when I try to generate the site in development i.e. "npm run generate", the dynamic route generator code cannot reach my local API running as HTTPS and fails with a "Nuxt Fatal Error: self signed certificate".
https://localhost:5001/api/page/main/generate 12:06:43
ERROR self signed certificate 12:06:43
at TLSSocket.onConnectSecure (node:_tls_wrap:1530:34)
at TLSSocket.emit (node:events:390:28)
at TLSSocket._finishInit (node:_tls_wrap:944:8)
at TLSWrap.ssl.onhandshakedone (node:_tls_wrap:725:12)
This worked 5 weeks ago and as far as I am aware I have not changed anything that should impact this. No software or packages have been updated (except windows updates perhaps). The API is still using .NET 5.0 and running on Kestrel using the default self signed cert on localhost (which is listed as valid in Windows). I simply added the payload to the routes, added the asyncData hook, and modified the page code accordingly.
I've Googled a few tidbits up but none have resolved the issue and now I'm at a loss. It really shouldn't be this blimmin opaque in 2022.
Tried disabling SSL via a proxy in nuxt.config;
proxy: {
'/api/': {
target: process.env.API_BASE_URL,
secure: !process.env.ENV === 'development'
}
}
Tried modifying my Axios plugin to ignore auth;
import https from 'https';
export default function ( { $axios } ) {
$axios.defaults.httpsAgent = new https.Agent( { rejectUnauthorized: false } );
}
And a variation of;
import https from 'https';
export default function ( { $axios, store } ) {
const agent = new https.Agent( {
rejectUnauthorized: false
} );
$axios.onRequest( config => {
if ( process.env.dev )
{
config.httpsAgent = agent;
}
} );
}
None of these 'worked for other people' solutions is working for me.
Also, the client side apps (public/admin) themselves have no problem working against my API locally, it's only the route builder within nuxt.config or asyncData code which is throwing this error.
Any suggestions would be appreciated. Happy to add other relevant code if needed, just not sure which atm.

Related

How can I configure Vite's dev server to give 404 errors?

Using Vite's dev server, if I try to access a non-existent URL (e.g. localhost:3000/nonexistent/index.html), I would expect to receive a 404 error. Instead I receive a 200 status code, along with the contents of localhost:3000/index.html.
How can I configure Vite so that it returns a 404 in this situation?
(This question: Serve a 404 page with app created with Vue-CLI, is very similar but relates to the Webpack-based Vue-CLI rather than Vite.)
Vite 3
Vite 3.x introduced appType, which can be used to enable/disable the history fallback. Setting it to 'mpa' disables the history fallback while keeping the index.html transform and the 404 handler enabled. The naming is somewhat misleading, as it implies the mode is only for MPAs, but on the contrary, you can use this mode for SPAs:
import { defineConfig } from 'vite'
export default defineConfig({
appType: 'mpa', // disable history fallback
})
Note the history fallback normally rewrites / to /index.html, so you'd have to insert your own middleware to do that if you want to keep that behavior:
import { defineConfig } from 'vite'
const rewriteSlashToIndexHtml = () => {
return {
name: 'rewrite-slash-to-index-html',
apply: 'serve',
enforce: 'post',
configureServer(server) {
// rewrite / as index.html
server.middlewares.use('/', (req, _, next) => {
if (req.url === '/') {
req.url = '/index.html'
}
next()
})
},
}
}
export default defineConfig({
appType: 'mpa', // disable history fallback
plugins: [
rewriteSlashToIndexHtml(),
],
})
Vite 2
Vite 2.x does not support disabling the history API fallback out of the box.
As a workaround, you can add a Vite plugin that removes Vite's history API fallback middleware (based on #ChrisCalo's answer):
// vite.config.js
import { defineConfig } from 'vite'
const removeViteSpaFallbackMiddleware = (middlewares) => {
const { stack } = middlewares
const index = stack.findIndex(({ handle }) => handle.name === 'viteSpaFallbackMiddleware')
if (index > -1) {
stack.splice(index, 1)
} else {
throw Error('viteSpaFallbackMiddleware() not found in server middleware')
}
}
const removeHistoryFallback = () => {
return {
name: 'remove-history-fallback',
apply: 'serve',
enforce: 'post',
configureServer(server) {
// rewrite / as index.html
server.middlewares.use('/', (req, _, next) => {
if (req.url === '/') {
req.url = '/index.html'
}
next()
})
return () => removeViteSpaFallbackMiddleware(server.middlewares)
},
}
}
export default defineConfig({
plugins: [
removeHistoryFallback(),
],
})
One disadvantage of this plugin is it relies on Vite's own internal naming of the history fallback middleware, which makes this workaround brittle.
You could modify fallback middleware to change the default behaves, or anything else you want. Here is an example. https://github.com/legend-chen/vite-404-redirect-plugin
Here's an approach that doesn't try to check what's on disk (which yielded incorrect behavior for me).
Instead, this approach:
removes Vite's SPA fallback middleware
it uses Vite's built-in HTML transformation and returns /dir/index.html (if it exists) for /dir or /dir/ requests
404s for everything else
// express not necessary, but its API does simplify things
const express = require("express");
const { join } = require("path");
const { readFile } = require("fs/promises");
// ADJUST THIS FOR YOUR PROJECT
const PROJECT_ROOT = join(__dirname, "..");
function removeHistoryFallback() {
return {
name: "remove-history-fallback",
configureServer(server) {
// returned function runs AFTER Vite's middleware is built
return function () {
removeViteSpaFallbackMiddleware(server.middlewares);
server.middlewares.use(transformHtmlMiddleware(server));
server.middlewares.use(notFoundMiddleware());
};
},
};
}
function removeViteSpaFallbackMiddleware(middlewares) {
const { stack } = middlewares;
const index = stack.findIndex(function (layer) {
const { handle: fn } = layer;
return fn.name === "viteSpaFallbackMiddleware";
});
if (index > -1) {
stack.splice(index, 1);
} else {
throw Error("viteSpaFallbackMiddleware() not found in server middleware");
}
}
function transformHtmlMiddleware(server) {
const middleware = express();
middleware.use(async (req, res, next) => {
try {
const rawHtml = await getIndexHtml(req.path);
const transformedHtml = await server.transformIndexHtml(
req.url, rawHtml, req.originalUrl
);
res.set(server.config.server.headers);
res.send(transformedHtml);
} catch (error) {
return next(error);
}
});
// named function for easier debugging
return function customViteHtmlTransformMiddleware(req, res, next) {
middleware(req, res, next);
};
}
async function getIndexHtml(path) {
const indexPath = join(PROJECT_ROOT, path, "index.html");
return readFile(indexPath, "utf-8");
}
function notFoundMiddleware() {
const middleware = express();
middleware.use((req, res) => {
const { method, path } = req;
res.status(404);
res.type("html");
res.send(`<pre>Cannot ${method} ${path}</pre>`);
});
return function customNotFoundMiddleware(req, res, next) {
middleware(req, res, next);
};
}
module.exports = {
removeHistoryFallback,
};
What's funny is that Vite seems to take the stance that:
it's a dev and build tool only, it's not to be used in production
built files are meant to be served statically, therefore, it doesn't come with a production server
However, for static file servers:
some configurations of static file servers will return index files when a directory is requested
they generally don't fallback to serving index.html when a file is not found and instead return a 404 in those situations
Therefore, it doesn't make much sense that Vite's dev server has this fallback behavior when it's targeting production environments that don't have it. It would be nice if there were a "correct" way to just turn off the history fallback while keeping the rest of the serving behavior (HTML transformation, etc).

How to make NextAuth.js work with SSG (static site generated) in a Next.js website

Next.js allows you to build your site with either server-side (SSR) or static client-side (SSG) rendering, but when you run next build && next export it removes the /api routes.
Since NextAuth.js relies on these routes to for /api/auth/signin for example, how can you implement NextAuth.js for SSG?
Yes next-auth can validate both backend and frontend.
The function you want is getSession() which is available both on the backend and frontend. However you cannot do that with statically generated sites, as they are evaluated on build. You can do that with serverside rendering though.
Here is a sample Server Side rendered Page Auth Guard
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
permanent: false,
destination: "/login",
},
};
}
return {
props: {
session,
},
};
}
Here is a sample API auth
import { getSession } from "next-auth/client"
export default async (req, res) => {
const session = await getSession({ req })
/* ... */
res.end()
}

How can I fix nuxt js static site links leading to network error?

I have a very basic nuxt.js application using JSON in a local db.json file, for some reason the generated static site links leading to network error, but I can access them from the url or page refresh.
nuxt config
generate: {
routes () {
return axios.get('http://localhost:3000/projects')
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
},
main root index page
data() {
return {
projects: []
}
},
async asyncData({$axios}){
let projects = await $axios.$get('http://localhost:3000/projects')
return {projects}
}
single project page
data() {
return {
id: this.$route.params.id
}
},
async asyncData({params, $axios}){
let project = await $axios.$get(`http://localhost:3000/projects/${params.id}`)
return {project}
}
P.S. I have edited the post with the code for the main and single project page
Issues with server-side requests of your application are caused by conflicts of ports on which app and json-server are running.
By default, both nuxt.js and json-server run on localhost:3000 and requests inside asyncData of the app sometimes do not reach correct endpoint to fetch projects.
Please, check fixed branch of your project's fork.
To ensure issue is easily debuggable, it is important to separate ports of API mock server and app itself for dev, generate and start commands.
Note updated lines in nuxt.config.js:
const baseURL = process.env.API_BASE_URL || 'http://localhost:3000'
export default {
server: {
port: 3001,
host: '0.0.0.0'
},
modules: [
['#nuxtjs/axios', {
baseURL
}]
],
generate: {
async routes () {
return axios.get(`${baseURL}/projects`)
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
}
}
This ensures that API configuration is set from a single source and, ideally, comes from environmental variable API_BASE_URL.
Also, app's default port has been changed to 3001, to avoid conflict with json-server.
asyncData hooks have been updated accordingly to pass only necessary path for a request. Also, try..catch blocks are pretty much required for asyncData and fetch hooks, to handle error correctly and access error specifics.

CORS axios Nuxt.js

I'm using an API with my Nuxt, here is my nuxt.config.js to allow the requests :
axios: {
prefix: '/api/',
proxy: true,
},
proxy: {
'/api/': {
target: process.env.API_URL || 'http://localhost:1337',
pathRewrite: {
'^/api/': '/',
}
},
}
On a specific page of my app, I need to send requests to another API from another domain. I'm using axios direclty in this Vue component :
axios.post('mysite.com/widget.rest')
As response, I obtain CORS error. Can I allow multiple API in my config ?
If you mean being able to access APIs under different URLs, AFAIK it's not possible out of the box. We tried adding proxy to different targets, but it only worked on client side, not during SSR.
What we ended up doing is having the default axios instance for our main API, and creating a plugin that creates extra axios instances for our other APIs for SSR.
Add extra "suburl" to proxy - this sorts out client side and means no CORS issues.
proxy: {
'/forum/': {
target: 'https://other.domain.com/',
pathRewrite: {'^/forum/': '/'}
},
'/api/': ...
},
For SSR to work, axios should hit directly the other API
import Vue from 'vue'
var axios = require('axios');
const forumAxios = axios.create(process.client ? {
baseURL: "/"
} : {
baseURL: 'https://other.domain.com/'
});
// this helps Webstorm with autocompletion, otherwise should not be needed
Vue.prototype.$forumAxios = forumAxios;
export default function (context, inject) {
if (process.server) {
forumAxios.interceptors.request.use(function (config) {
config.url = config.url.replace("/forum/", "");
return config;
}, function (error) {
return Promise.reject(error);
});
}
inject('forumAxios', forumAxios);
In components, you can then use something like:
async asyncData({app}) {
let x = await app.$forumAxios.get("/forum/blah.json");
You can use process.env prop of course instead of hardcoded URL.
This will hit https://other.domain.com/blah.json.

Nuxt Ava End-to-End Testing Store Configuration

Given the example official Nuxt end-to-end test example using Ava:
import test from 'ava'
import { Nuxt, Builder } from 'nuxt'
import { resolve } from 'path'
// We keep a reference to Nuxt so we can close
// the server at the end of the test
let nuxt = null
// Init Nuxt.js and start listening on localhost:4000
test.before('Init Nuxt.js', async t => {
const rootDir = resolve(__dirname, '..')
let config = {}
try { config = require(resolve(rootDir, 'nuxt.config.js')) } catch (e) {}
config.rootDir = rootDir // project folder
config.dev = false // production build
config.mode = 'universal' // Isomorphic application
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
nuxt.listen(4000, 'localhost')
})
// Example of testing only generated html
test('Route / exits and render HTML', async t => {
let context = {}
const { html } = await nuxt.renderRoute('/', context)
t.true(html.includes('<h1 class="red">Hello world!</h1>'))
})
// Close the Nuxt server
test.after('Closing server', t => {
nuxt.close()
})
How can you use Nuxt or Builder to configure/access the applications Vuex store? The example Vuex store would look like:
import Vuex from "vuex";
const createStore = () => {
return new Vuex.Store({
state: () => ({
todo: null
}),
mutations: {
receiveTodo(state, todo) {
state.todo = todo;
}
},
actions: {
async nuxtServerInit({ commit }, { app }) {
console.log(app);
const todo = await app.$axios.$get(
"https://jsonplaceholder.typicode.com/todos/1"
);
commit("receiveTodo", todo);
}
}
});
};
export default createStore;
Currently trying to run the provided Ava test, leads to an error attempting to access #nuxtjs/axios method $get:
TypeError {
message: 'Cannot read property \'$get\' of undefined',
}
I'd be able to mock $get and even $axios available on app in Vuex store method nuxtServerInit, I just need to understand how to access app in the test configuration.
Thank you for any help you can provide.
Just encountered this and after digging so many tutorial, I pieced together a solution.
You have essentially import your vuex store into Nuxt when using it programmatically. This is done by:
Importing Nuxt's config file
Adding to the config to turn off everything else but enable store
Load the Nuxt instance and continue your tests
Here's a working code (assuming your ava and dependencies are set up)
// For more info on why this works, check this aweomse guide by this post in getting this working
// https://medium.com/#brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28
import test from 'ava'
import jsdom from 'jsdom'
import { Nuxt, Builder } from 'nuxt'
import nuxtConfig from '../nuxt.config' // your nuxt.config
// these boolean switches turn off the build for all but the store
const resetConfig = {
loading: false,
loadingIndicator: false,
fetch: {
client: false,
server: false
},
features: {
store: true,
layouts: false,
meta: false,
middleware: false,
transitions: false,
deprecations: false,
validate: false,
asyncData: false,
fetch: false,
clientOnline: false,
clientPrefetch: false,
clientUseUrl: false,
componentAliases: false,
componentClientOnly: false
},
build: {
indicator: false,
terser: false
}
}
// We keep a reference to Nuxt so we can close
// the server at the end of the test
let nuxt = null
// Init Nuxt.js and start listening on localhost:5000 BEFORE running your tests. We are combining our config file with our resetConfig using Object.assign into an empty object {}
test.before('Init Nuxt.js', async (t) => {
t.timeout(600000)
const config = Object.assign({}, nuxtConfig, resetConfig, {
srcDir: nuxtConfig.srcDir, // don't worry if its not in your nuxt.config file. it has a default
ignore: ['**/components/**/*', '**/layouts/**/*', '**/pages/**/*']
})
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
nuxt.listen(5000, 'localhost')
})
// Then run our tests using the nuxt we defined initially
test.serial('Route / exists and renders correct HTML', async (t) => {
t.timeout(600000) // Sometimes nuxt's response is slow. We increase the timeont to give it time to render
const context = {}
const { html } = await nuxt.renderRoute('/', context)
t.true(html.includes('preload'))
// t.true(true)
})
test.serial('Route / exits and renders title', async (t) => {
t.timeout(600000)
const { html } = await nuxt.renderRoute('/', {})
const { JSDOM } = jsdom // this was the only way i could get JSDOM to work. normal import threw a functione error
const { document } = (new JSDOM(html)).window
t.true(document.title !== null && document.title !== undefined) // simple test to check if site has a title
})
Doing this should work. HOWEVER, You may still get some errors
✖ Timed out while running tests. If you get this you're mostly out of luck. I thought the problem was with Ava given that it didn't give a descriptive error (and removing any Nuxt method seemed to fix it), but so far even with the above snippet sometimes it works and sometimes it doesn't.
My best guess at this time is that there is a delay on Nuxt's side using either renderRouter or renderAndGetWindow that ava doesn't wait for, but on trying any of these methods ava almost immediately "times out" despite the t.timeout being explicitly set for each test. So far my research has lead me to checking the timeout for renderAndGetWindow (if it exists, but the docs doesn't indicate such).
That's all i've got.