Nuxt: can't use beforeCreate or beforeMount to check data or redirect - hook

I want to have the following in my nuxt page:
beforeCreate() {
if (!this.$store.getters['experiences/current']) {
this.$router.push('/experiences');
}
},
Not 100% sure if I should use beforeCreate or beforeMount but the idea is, if the experiences/current getter is empty, go to the index page.
If I console.log(this.$store.getters['experiences/current']) it is actually empty, but the redirect doesn't happen and a template rendering error rises because it relies on this.$store.getters['experiences/current'].
So how can I make data validation before first component render and redirect if data is not present?

You can use a js middleware instead ...
For ts implementation ...
import { Middleware } from '#nuxt/types'
const myMiddleware: Middleware = ({ store, redirect }) => {
if (!store.state.applicationState && !store.state.yourProperty) {
return redirect('/')
}
}
export default myMiddleware

Related

How to use correctly NextJs API

i've just started using Next js with mongodb and i have a question about how should i organize the API route files.
I have a simple application that add, update and delete documents of a mongodb collection. For each operation i created a .ts file inside the api folder. Like this
And for example my new_task.ts file looks like this
export default async function AddTask (req:NextApiRequest, res:NextApiResponse) {
const task:Task = req.body
const client = await clientPromise;
const db = client.db("diary");
const myCollection: Collection = db.collection('tasks');
try {
await myCollection.insertOne(task)
res.send('Success')
} catch (error) {
res.status(400).json({error})
console.log(error)
}
}
Everything is working ok but i think it's kinda messy the file organization. Is there a way to put every operation inside just one file? Or to do so i would have to build a custom server with express?
Thanks
In one route function, you can check the request object req to see if the HTTP request method is GET POST PUT PATCH or DELETE. Depending on which method, you can call a different function.
Here is an example from the NextJS docs.
import type { NextApiRequest, NextApiResponse } from 'next'
export default function userHandler(req: NextApiRequest, res: NextApiResponse) {
const {
query: { id, name },
method,
} = req
switch (method) {
case 'GET':
// Get data from your database
res.status(200).json({ id, name: `User ${id}` })
break
case 'PUT':
// Update or create data in your database
res.status(200).json({ id, name: name || `User ${id}` })
break
default:
res.setHeader('Allow', ['GET', 'PUT'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
Another thing you can do to make your code more re-useable and easier to maintain is to write reusable function definitions in a lib folder and then import them into your api route files when you want to use them.
Have you tried creating a file in the lib folder and writing function definitions there for MongoDB and then importing those function definitions into your api route file?
Then call the appropriate function depending upon the request method.
In ./lib/mongodb, write a function definition and import any Mongo-related imports you need.
export async function updateUserInfo(parameters) {
// . . . your code needs to return something, probably an array or object from MongoDB
}
In your api route file, import that function definition.
import { updateUserInfo } from "../../lib/mongodb"
Inside your route function, call updateUserInfo and pass whatever arguments you need based on the parameters you put in the definition. Handle its return value using await.
import type { NextApiRequest, NextApiResponse } from 'next'
import { updateUserInfo } from "../../lib/mongodb"
export default function userHandler(req: NextApiRequest, res: NextApiResponse) {
const {
query: { id, name },
method,
} = req
switch (method) {
case 'GET':
// Get data from your database
res.status(200).json({ id, name: `User ${id}` })
break
case 'PUT':
// Update or create data in your database
const updateResult = await updateUserInfo( . . .)
// FIX THE OBJECT IN .JSON BELOW TO SUIT YOUR CODE
res.status(200).json({ id, name: name || `User ${id}` })
break
default:
res.setHeader('Allow', ['GET', 'PUT'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
You can reuse updateUserInfo anywhere you have arguments for the required parameters.
Also, consider when you are calling the API route. At build time or after. At build, you call from static functions and after you call from client-side.
So by using the lib file for function definitions, you can reuse them in server functions and static functions.
The structure of the files inside the api folder is your api architecture. So it's organization depends upon your application's needs. You can use static and dynamic routes, as you maybe already know.
Consider API best practices when designing your architecture.

Getting 500 Internal Server Error (using axios). Locally it gives error axiosError but it works fine when I hit refresh & everything loads [duplicate]

I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.

How can I properly create redirects from an array in Gatsby

I am working with Gatsby and WordPress. I am trying to redirect some URLs using the Gatsby redirect API. I write the query to get an Object and then I use the Map method to create an array of the items we need from that object. I then run a for Each method to get the individual data from that array but it fails on running the development server.
What is the Right way to do this?
const { createRedirect } = actions;
const yoastRedirects = graphql(`
{
wp {
seo {
redirects {
format
origin
target
type
}
}
}
}
`)
const redirectOriginUrls = yoastRedirects.wp.seo.redirects.map(redirect=>(redirect.origin))
const redirectTargetUrls = yoastRedirects.wp.seo.redirects.map(redirect=>(
redirect.target
))
redirectOriginUrls.forEach(redirectOriginUrl=>(
redirectTargetUrls.forEach(redirectTargetUrl=>(
createRedirect({
fromPath: `/${redirectOriginUrl}`,
toPath: `/${redirectTargetUrl}`,
isPermanent: true
})
))
))
The createRedirect API needs to recieve a structure like:
exports.createPages = ({ graphql, actions }) => {
const { createRedirect } = actions
createRedirect({ fromPath: '/old-url', toPath: '/new-url', isPermanent: true })
createRedirect({ fromPath: '/url', toPath: '/zn-CH/url', Language: 'zn' })
createRedirect({ fromPath: '/not_so-pretty_url', toPath: '/pretty/url', statusCode: 200 })
// Create pages
}
In your case, you are not entering to the correct fetched data. Assuming that the loops are properly done, you must do:
let redirectOriginUrls=[];
let redirectTargetUrls=[];
yoastRedirects.data.wp.seo.redirects.map(redirect=>{
return redirectOriginUrls.push(redirect.origin)
});
yoastRedirects.data.wp.seo.redirects.map(redirect=>{
return redirectTargetUrls.push(redirect.target)
})
Instead of:
const redirectOriginUrls = yoastRedirects.wp.seo.redirects.map(redirect=>(redirect.origin))
const redirectTargetUrls = yoastRedirects.wp.seo.redirects.map(redirect=>(
redirect.target
))
Notice the .data addition in the nested object.
In addition, keep in mind that the createRedirect API will only work only when having a hosting infrastructure behind, like AWS or Netlify, both have plugins integration with Gatsby. This will generate meta redirect HTML files for redirecting on any static file host.

Vue.js authentication after manually entering URL

In my routes array in main.js, I have set
meta : { requiresAuth: true }
for every component except on the UserLogin page.
Regarding navigation resolution, I have set a check before each route as follows:
router.beforeEach((to, from, next) => {
const currentUser = firebase.auth().currentUser;
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
const currentUserState = !!currentUser;
console.table({ currentUserState, requiresAuth });
if (requiresAuth && !currentUser) {
next('/login');
} else {
next();
}
}
With this setup, everything almost works as expected/intended. Logged in users can go anywhere, and anonymous users are redirected to localhost/login.
However, this checking and redirecting only functions when users are navigating by clicking the links in the web app (which are all <router-link>s that go :to="{ name: 'SomeComponent' }".
When any user (registered or anonymous) attempts to navigate their way around the app by typing or copy pasting urls, the app will automatically send them to the login page.
From the console.table output, I can see that manually entering urls always results in currentUserState being false.
It does not remain false: a user that has logged in (currentUserState === true), who then manually navigates somewhere (currentUserState === false), has their currentUserState revert back to true
I can only vaguely guess that the problem is related to rendering and firebase.auth().
firebase.initializeApp(config)
// you need to let firebase try and get the auth state before instantiating the
// the Vue component, otherwise the router will be created and you haven't
// resolved whether or not you have a user
firebase.auth().onAuthStateChanged(function(user) {
app = new Vue({
el: '#app',
template: '<App/>',
components: { App },
router
})
});
I have found a solution to this bug.
In my Vuex store.js, a snapshot of the state is stored into localStorage. This snapshot is updated any time there is a change to the store itself, so the localStorage backup is always up-to-date with the actual state of the Vue.js application itself.
store.subscribe((mutation, state) => {
localStorage.setItem('store', JSON.stringify(state));
});
This store.subscribe is set in my store.js just before the export line.
A function called initStore is also defined in the store as follows
initStore(state) {
if (localStorage.getItem('store')) {
this.replaceState(Object.assign(state, JSON.parse(localStorage.getItem('store'))));
}
},
and after my store object, it is immediately called with
store.commit('initStore').

SailsJS : Redirecting to controller action with variables

I am using sailsJS with ejs engine and i want to redirect the user back to the input page with messages ( validation errors ... ) .
i used to use this easily with laravel in PHP ( return redirect('dashboard')->with('status', 'Profile updated!'); )
i.e : i need to redirect the user back saying that this site dont exist
find : function(req,res){
var id = req.param(íd');
Site.find(id).where({isDeleted : null })
.exec(function(err,siteFound){
if(err) console.log(err);
if(siteFound) {
return res.view('site/show', {
site : siteFound
});
} else {
return res.redirect('/site');
}
})
},
i searched in sails documentation but found nothing. how can this be performed in SailsJS ?
thanks
UPDATE : i found what i needed exactly by installing sails-hook-flash . the feature i needed is called flash messages.
Thank you for your help !
Blockquote
I can't quite tell if you want a true browser redirect. A browser redirect means sending a message back to the browser that says "use this other url instead", and then it gets fresh data (meaning new req and res objects) from your app. If this is what you want, I'd say the only real options for passing data are query strings, like:
return res.redirect('/site?message=notfound');
Then in your recieving controller action for site you can access this via req.param('message').
However, if you just want to return the appropriate content now without getting the browser to redirect, you can just call whatever view or controller action you like:
if(siteFound) {
return res.view('site/show', {
site : siteFound
});
} else {
// uncomment one of the options:
// ONE: return another view
// return res.view('site/notfound, {requested: id});
// TWO: pass to another controller action in same controller
// req.options.message = 'not found';
// return module.exports.someOtherAction(req, res);
// THREE: pass to a controller action in another controller
// req.options.message = 'not found';
// var OtherController = require('./OtherController');
// return OtherController.someOtherAction(req, res);
}
Any of those three options will keep you at the user's requested url ("/site/abc123" or whatever), but display the content you specify.
res.notfound("my message string"); should do it right?
You can work with res.json() if it is an ajax request expecting a custom response.
Read the docs about the res object HERE and the custom notfound response HERE.

Resources