v-on:click never firing on button in Nuxt component, because of middleware - vue.js

I've seen this elsewhere in other questions, but haven't seen a solution. I'm trying to make a simple GDPR cookie notification that closes on click, using a button. I'm in Universal mode, meaning mounted() isn't available, so I'm trying to set cookies through Vuex. However, the click event I have bound to the button in my component isn't firing.
Edit: After building a codesandbox version of my app, which worked as it should, I went through and hacked up my Nuxt app until I found what was causing the problem. As it turns out, it was my middleware, and more specifically, the fact that I was using the fs-extra library to read JSON files. Still not clear on why this is happening, so any suggestions are welcome. The code below includes my middleware.
components/CookieNotice.vue
<template>
<div v-if="cookie != true" class="cookie_notice_wrap">
<div class="cookie_notice">
<p class="notice_message">This site uses cookies for analytics purposes.</p>
<button #click.prevent="dismissNotification" class="notice_dismiss">Close</button>
</div></div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "CookieNotice",
methods: {
dismissNotification: function(e) {
console.log("we clicked?");
document.querySelector(".cookie_notice_wrap").classList.add("hidden_click");
this.store.dispatch("cookieStateChange", true);
}
},
computed: {
...mapGetters(["cookie"]),
}
}
</script>
Actions from store/index.js
export const actions = {
async getPosts({ state, commit, dispatch }) {
if (state.posts.length) {
return
}
try {
await axios.get("/api/json-access");
}
catch (err) {
console.log(err);
}
},
nuxtServerInit({ state, commit, dispatch }, { req }) {
dispatch("getPosts");
const seen = this.$cookies.get("cookie_notice_seen");
if (!seen) {
dispatch("cookieStateChange", false);
}
},
cookieStateChange({state, commit, dispatch}, bool) {
// this does set the cookie correctly, unsure if it will work the same when bound to the button click
commit("updateCookie", bool);
this.$cookies.set("cookie_notice_seen", bool, {
path: "/",
maxAge: 60 * 60 * 24 * 7
});
}
}
~/middleware/api/json-access.js:
const fse = require('fs-extra');
import axios from 'axios';
const storeLocation = "middleware/full_site.json";
export default async function({ store, route, redirect }) {
const exists = await fse.pathExists(storeLocation);
let posts;
if (!exists) {
await fse.ensureFile(storeLocation);
posts = await postsFromWP();
fse.writeJSON(storeLocation, posts);
}
else {
try {
posts = await fse.readJSON(storeLocation);
}
catch (err) {
console.log(err);
}
}
store.commit("updatePosts", posts);
}
async function postsFromWP() {
const url = ".../example/json/file.json";
const config = { headers: { "Accept": "application/json" }};
let posts = await axios.get(url, config);
posts = posts.data
.filter(el => el.status === "publish")
.map(({ id, slug, title, excerpt, date, tags, content }) => ({
id, slug, title, excerpt, date, tags, content
}));
return posts;
}
I had the middleware configured in nuxt.config.js via routes -> middleware before, but currently have it set to go through serverMiddleware instead, for testing. I also added the action that triggers getting the posts, in case that's also part of it. This has definitely hit on a limit of my Nuxt/Vue understanding - I have no idea why this could be happening, so any wisdom is much appreciated.

If you wind up dealing with something similar, fs or fs-extra are your culprits. You can't use file operations on the client side, which is when these middleware actions are happening (I think). The cookie notice only fully worked when I removed the fs-extra import at the very top of the middleware file.

Related

How to check authentication in SvelteKit?

I want to check if the user is logged in when they visit the route /login and, if so, redirect them to /. The same happens vice versa if they are not logged in as well.
I want to put something like:
export async function load() {
const res = await fetch("[endpoint]", {
headers: {
"Authorization": `Bearer ${localStorage.jwt}`
},
credentials: "include"
});
const json = await res.json();
return {
logged_in: json.logged_in
};
}
in my +page.js (or +page.server.js?) but this doesn't work as it says localStorage is undefined.
How should I go about doing this?
LocaleStorage in Sveltekit is a bit tricky, but there are ways around it, like checking to see if your code is being executed on the client's browser, like so:
import { browser } from '$app/env'
export async function load(){
if (browser) {
// Do stuff here
...
}
...
}
A solution that's worked for me is chucking this code into the <script context="module"> of a base __layout.svelte that every layout inherits from.
For instance, you could have a __layout.svelte that consists of nothing more than this code. Building off of what Stephane pointed out, if you don't want to use cookies (e.g. a jwt token with a very limited lifespan) you could use session storage;
<script context="module">
export async function load({ fetch, session }) {
const res = await fetch("[endpoint]", {
headers: {
"Authorization": `Bearer ${session.jwt}`
},
credentials: "include"
});
const json = await res.json();
return {
logged_in: json.logged_in
};
}
</script>
<slot />
(You can read more about load() here)
And then have other layouts that inherit this layout by having a layout like __layout-auth#default.svelte. You can read more about named layouts here.

How to Implement nuxtServerInit Action to load data from server-side on the initial load in Pinia (Nuxt3)

My Code:
export const useMenuStore = defineStore("menuStore", {
state: () => ({
menus: [],
}),
actions: {
async nuxtServerInit() {
const { body } = await fetch("https://jsonplaceholder.typicode.com/posts/1").then((response) => response.json());
console.log(body);
this.menus = body;
resolve();
},
},
});
NuxtServerInit is not working on initial page render on nuxt js vuex module mode.Anyone know this error please help me.
NuxtServerInit is not implemented in Pinia, but exists a workaround.
Using Pinia alongside Vuex
// nuxt.config.js
export default {
buildModules: [
'#nuxtjs/composition-api/module',
['#pinia/nuxt', { disableVuex: false }],
],
// ... other options
}
then Include an index.js file inside /stores with a nuxtServerInit action which will be called from the server-side on the initial load.
// store/index.js
import { useSessionStore } from '~/stores/session'
export const actions = {
async nuxtServerInit ({ dispatch }, { req, redirect, $pinia }) {
if (!req.url.includes('/auth/')) {
const store = useSessionStore($pinia)
try {
await store.me() // load user information from the server-side before rendering on client-side
} catch (e) {
redirect('/auth/login') // redirects to login if user is not logged in
}
}
}
}
In Nuxt2, the Nuxt will run the code in nuxtServerInit() of store/index.js on the server-side to boot the app.
However, in Nuxt3, there is no specific place to write the boot code, you can write the boot code anywhere instead of in nuxtServerInit() of store/index.js.
It might be helpful, especially when you need to send a request before boosting the app.
your pinia file may define like following:
store/menu.js
import { defineStore } from 'pinia';
export const useMenuStore = defineStore('menuStore', {
state: () => ({
_menus: [],
}),
getters: {
menus() {
return this._menus;
}
},
actions: {
async boot() {
const { data } = await useFetch('https://jsonplaceholder.typicode.com/posts/1');
this._menus = data;
}
}
});
Then, create a plugin which named as *.server.[ts|js], for example init.server.js
(.sever.js tail will let the file only run in server side)
plugins/init.server.js
import { defineNuxtPlugin } from '#app';
import { useMenuStore } from '~/store/menu.js';
export default defineNuxtPlugin(async (nuxtApp) => {
const menu = useMenuStore(nuxtApp.$pinia);
await menu.boot();
});
nuxt.config.js
modules: [
'#pinia/nuxt',
],
There is an entire example of SSR Nuxt3 with authorization that may help

vue/vuex: Can you re-render a page from another page?

With the first login in my app, users get a possibility to leave their address. When this address is stored, the user are pushed to their dashboard. Second login the user go straight to the dashboard.
I have 2 Vuex states that are updated with the response.data. 'Signed' leads to address page, 'Frequent' leads to 'dashboard'.
//PROMPT.VUE
mounted () {
this.getPrompt()
},
computed: {
promptStatus () {
return this.$store.getters.getPrompt
}
},
methods: {
async getPrompt() {
try{
await //GET axios etc
// push prompt status in Store
let value = response.data
this.$store.commit('setPrompt', value)
if (this.promptStatus === 'signed') {
this.$router.push({path: '/adres'})
}
if (this.promptStatus === 'frequent') {
this.$router.push({path: '/dashboard'})
}
When user leaves the address I reset the vuex.state from 'signed' to 'frequent'.
//ADRES.VUE
//store address
let value = 'frequent'
this.$store.commit('setPrompt', value)
this.$router.push({name: 'Prompt'})
The Vuex.store is refreshed. But the Prompt.vue wil not re-render with the new vuex.status. Many articles are written. Can 't find my solution. Maybe I organize my pages the wrong way.
In views, it is not recommended to mutate data (call commit) outside vuex. Actions are created for these purposes (called from the component using dispatch). In your case, you need to call action "getPrompt" from the store, but process routing in the authorization component. This is more about best practice
To solve your problem, you need to make a loader when switching to dashboard. Until the data is received, you do not transfer the user to the dashboard page
Example
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "DashboardLayout",
components: { ..., ... },
data: () => ({
isLoad: false
}),
async created() {
this.isLoad = false;
try {
await this.$store.dispatch('getData');
this.isLoad = true;
} catch (error) {
console.log(error)
}
}
});
</script>
Data is received and stored in the store in the "getData" action.
The referral to the dashboard page takes place after authorization. If authorization is invalid, the router.beforeEach handler (navigation guards) in your router/index.js should redirect back to the login page.
Learn more about layout in vuejs
Learn more about navigation guards

Nuxt ServerMiddleware Returning HTML Doc

I am building out a webpage which needs to make a call to the Google Geocoder api.
In order to hide the api key from public view, I am trying to set up server middleware to act as a REST api endpoint.
I have checked through all of the documentation and copied all of it, but the response is always the same. I receive the entirety of the html body back from the axios request rather than anything else I send back via express.
In my component I have the following code:
computed: {
normalizedAddress() {
return `${this.member.address.street} ${this.member.address.city}, ${this.member.address.state} ${this.member.address.zip}`.replace(
/\s/g,
'+'
)
}
},
methods: {
async getLocation() {
try {
const res = await axios.get(
`/api/geocode/${this.normalizedAddress}`
)
console.log(res)
} catch (err) {
console.log(err)
}
}
},
In nuxt.config.js I have this setup
serverMiddleware: ['~/api/geocode.js'],
In the root of my project I have an api folder with geocode.js stored there.
geocode.js is below
import express from 'express';
import axios from "axios";
let GEO_API = "MY_API_KEY"
const app = express();
app.use(express.json());
app.get("/", async (req, res) => {
const uri = `https://maps.googleapis.com/maps/api/geocode/json?address=${req.params.address}&key=${GEO_API}`
try {
const code = await axios.get(uri);
if (code.status !== "OK") {
return res.status(500).send(code.status)
}
return res.status(200).send(code);
} catch (err) {
return res.status(500).send(err);
}
});
export default {
path: "/api/geocode/:address",
handler: app
}
Again. The response always has the entire html document from the website sent back (some 100 pages of code).
Even when I set the response to fixed text, that is not sent.
The only detail I can think of that might be interrupting it is that I have my own custom routing setup using the #nuxtjs/router build module in use.

NextJS consistently access request object for every page

I'm using express + passport + nextjs to set up an app that will perform authentication using OpenID Connect. The user data is stored on the request object using express-session which gives me req.user on every request as usual.
Now I want to pass the user information to the front-end so that I can use it for something, but there does not seem to be any consistent way to do this for all requests. I can use getServerSideProps for individual pages, but not for every page through either _document or _app. How can I set this up?
Here is my current _document.tsx
import Document, {
Head,
Main,
NextScript,
DocumentContext,
} from "next/document"
export default class Doc extends Document {
public static async getInitialProps(ctx: DocumentContext) {
const req: any = ctx.req
console.log("req/user", `${!!req}/${!!(req && req.user)}`)
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
user: req?.user || "no user",
}
}
public render() {
return (
<html>
<Head />
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}
It appears to return a request object only during the very first request, not any subsequent refreshes of the page.
I've created a small repo that reproduces the issue here: https://github.com/rudfoss/next-server-custom-req
It seems ridiculous that there is no way to do this for all pages in an easy manner.
Edit: For reference this is my server.js. It is the only other relevant file in the repo
const express = require("express")
const next = require("next")
const dev = process.env.NODE_ENV !== "production"
const start = async () => {
console.log("booting...")
const server = express()
const app = next({ dev, dir: __dirname })
const handle = app.getRequestHandler()
await app.prepare()
server.use((req, res, next) => {
req.user = {
authenticated: false,
name: "John Doe",
}
next()
})
server.get("*", handle)
server.listen(3000, (err) => {
if (err) {
console.error(err)
process.exit(1)
}
console.log("ready")
})
}
start().catch((error) => {
console.error(error)
process.exit(1)
})
It is recommended to do this via function components, as seen in the Next.js custom App docs:
// /pages/_app.tsx
import App, { AppProps, AppContext } from 'next/app'
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
MyApp.getInitialProps = async (appContext: AppContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext)
const req = appContext.ctx.req
return {
pageProps: {
...appProps.pageProps,
user: req?.user,
},
}
}
As in your answer, this will run on every request though so automatic static optimization will not be active.
Try a demo of changing pageProps in MyApp.getInitialProps (without usage of req.user) on the following CodeSandbox:
https://codesandbox.io/s/competent-thompson-l9r1u?file=/pages/_app.js
Turns out I can override getInitialProps on _app to make this work:
class MyApp extends App {
public static async getInitialProps({
ctx
}: AppContext): Promise<AppInitialProps> {
const req: any = ctx.req
return {
pageProps: {
user: req?.user
}
}
}
public render() {
//...
}
}
This will run on every request though so static optimization will not work, but in my case I need the information so I'm willing to accept the trade-off.
Edit: This answer also works, but it uses the "old" class-based component syntax which is no longer recommended. See answer from Karl for a more modern version using functional-component syntax.
I also had the similar problem where I had to fetch loggedIn user details from my Auth api. I solved it by wrapping my whole app inside a context provider, then using a set function for the initialState, which will remember if it was called before and fetch user details only once. Then in my each page, wherever I require these user details, I used the context to see if details are available and call the set function if details are not available. This way I think I achieved:
Only one request to fetch user details
Because it happens from the client side, TTFB is better
I can still take advantage of getStaticProps and getServerSideProps where it is required.