Next.js Router.push does not set any req.headers - javascript

I have an issue with doing backend queries in getInitialProps function after executing client side Router.push() from another endpoint.
Here is what I mean. In my signup page, I call Router.push('/') to return to the home page:
proceedToIndex = () => {
Router.push('/');
}
In my '/' page, I call getInitialProps as follows:
static async getInitialProps (context) {
try {
if (context.req.headers.cookie) {
const resp = await getUser(context.apolloClient);
if (resp.data.getUser && resp.data.getUser.id) {
return { user: resp.data.getUser };
}
};
} catch(e) {
console.log(e);
}
return { user: undefined };
}
I end up with a crash saying cannot call cookie of undefined for context.req.headers.cookie. So headers is undefined when I execute Router.push('/'). What is going on here and how do I feed headers into my request object in context?

There are no requests when using Router.push. It's a client-side API, and requests only exist in the initial server render.

Related

Nextjs: getInitialProps doesn't not run on Higher order component on page redirect using LINK or Button

I Got following piece of code on my higher order component (AuthLayout).This function works fine on page reload but when i redirect from one Link to another it seems not working and it loses all the existing context.
please help me to find out what i did wrong here?
I am using Nextjs Version 12.2.5
Thanks in Advance.
static async getInitialProps(context) {
try {
const token = await getFirebaseCookie('id_token', context);
const isLoggedIn = token ? true : false;
return {
isLoggedIn,
token
};
} catch (error) {
console.log(error)
}
}
Update:
my getFirebaseCookie function looks like this.
const getFirebaseCookie = (key, context = false) => {
// get cookie from __session using getCookie function
// parse the data from cookie
// get the relatedData using the key
try {
const cookieData = getCookie(FIREBASE_COOKIE, context);//client and server both
const data = cookieData ? cookieData: {};
if (data && data.hasOwnProperty(key)) {
return data[key];
} else {
console.log("not found")
}
} catch (error) {
console.log(error, 'getFirebaseCookie');
}
};
See this article: https://blog.logrocket.com/getinitialprops-vs-getserversideprops-nextjs/
getInitialProps is considered legacy; try using getServerSideProps instead and see how it works. I think it boils down to how they work on page transitions: they both fetch data on the server on the initial page load, but on page transitions (with next/link), getInitialProps runs on the client, so if an API is inaccessible, or at least behaves differently, on the client, it will not work the way you intend.

Authenticated requests after sign in with React Query and NextAuth

I'm having troubled sending an authenticated request to my API immediately after signing in to my Nextjs app using NextAuth. The request that is sent after signing in returns data for and unauthenticated user.
I believe the issue is that React Query is using a previous version of the query function with an undefined jwt (which means its unauthenticated). It makes sense because the query key is not changing so React Query does not think it's a new query, but, I was under the impression that signing in would cause loading to be set to true temporarily then back to false, which would cause React Query to send a fresh request.
I've tried invalidating all the queries in the app using queryClient, but that did not work. I've also used React Query Devtools to invalidate this specific query after signing in but it still returns the unauthenticated request. Only after refreshing the page does it actually send the authenticated request.
// useGetHome.js
const useGetHome = () => {
const [session, loading] = useSession();
console.log(`session?.jwt: ${session?.jwt}`);
return useQuery(
'home',
() => fetcher(`/home`, session?.jwt),
{
enabled: !loading,
},
);
}
// fetcher
const fetcher = (url, token) => {
console.log(`token: ${token}`);
let opts = {};
if (token) {
opts = {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}${url}`, opts);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}
return res.json();
}
// Home.js
const Home = () => {
const { data: home_data, isLoading, error } = useGetHome();
...
return(
...
)
}
Attached is the console immediately after signing in. You can see the the session object contains the jwt after signing in, but in the fetcher function it is undefined.
console after signing in
Any help here is appreciated. Is there a better way to handle authenticated requests using React Query and NextAuth? Thank you!
I have tried a similar situation here and struggled the same thing but the enabled property worked fine for me and it is good to go right now.
https://github.com/maxtsh/music
Just check my repo to see how it works, that might help.

how to cleanly handle errors in nextjs getStaticProps

I'm very busy at the moment with building my first Next.JS application (Next and Strapi). Now everything is working but i'm curious about what the best way is to implement error handling when using getStaticProps.
I tried a few things myself (passing multiple props etc, but that all didn't work (typical unserialized JSON error). The thing I want to achieve is an error message on the page itself (e.g. /about) that no data was found. With an error message attached (statusCode).
I hope it's possible, I did a lot research and found: https://github.com/vercel/next.js/pull/17755 this. But it's not exactly what I'm looking for.
You can create custom 404 and 500 error pages. There is an option to show the statusCode however, you can tell Next to use the 404 page by returning notfound: true in getStaticProps.
If you return notfound: true, the statusCode will always show the 404 page, and you know the status code will be 404.
Here is a example of catching errors in getStaticProps- this will generate your page or show your custom error page that is designed to your specifications.
export const getStaticProps = async () => {
try {
const { data, errors } = await someQuery();
if (errors || !data) {
return { notFound: true };
}
return { props: { data } };
} catch () {
return { notFound: true };
}
};
A not so obvious additional use case of notFound is to use it to exclude a directory or page from production. The below check will skip the whole page or directory during next/export (SSG). This check be used to produce development only static pages.
If a single page in a directory has the check below, that page is skipped during the build process. If every page in a directory has the check - the whole directory will be skipped, including the folder.
export const getStaticProps = async () => {
if (process.env.NODE_ENV === 'production') {
return { notFound: true };
}
...
};
Make sure you don't include the routes you don't want built in getStaticPaths too.
This worked for me.
api.js
export async function fetcher(url, options = {}) {
try {
let response;
if (!options) {
response = await fetch(url);
} else {
response = await fetch(url, options);
}
const data = await response.json();
return data;
} catch (error) {
return {
notFound: true,
};
}
}
in pages/index.js
import { fetcher } from "/lib/api";
...
export async function getStaticProps() {
const { data, notFound } = await fetcher(
`${process.env.NEXT_PUBLIC_STRAPI_API}/homepage?publicationState=live&populate[seo][populate]=%2A&populate[pageHeading][populate]=%2A&populate[socialMedia][populate]=%2A&populate[block][populate]=%2A`
);
if (notFound) {
return {
notFound: true,
};
}
return { props: { data } };
}

How to display 404 page if a back-end GET request to an API fails because user doesn't exists? Separated front-end and back-end

I have an application that uses JavaScript with Vue.js for the front-end and PHP with Laravel for the back-end.
Right now, when I make a GET request from my front-end to my back-end on URL /getSummoner/{summonerName}, I make another GET request from my back-end to a third party API in order to get the details for a user with a certain summoner name like this:
public function getSummoner($summonerName){
$summoner = Summoner::where('summoner_name', $summonerName)->first();
if ($summoner === null) {
$apiKey = env("RIOT_API_KEY");
$region = env("EUW");
$getSummonerInfo = file_get_contents($region . "/lol/summoner/v4/summoners/by-name/" . $summonerName . "?api_key=" . $apiKey);
$summonerInfo = json_decode($getSummonerInfo);
$summoner = new Summoner();
$summoner->summoner_name = $summonerName;
$summoner->summoner_info = json_encode($summonerInfo);
$summoner->save();
} else {
$summonerInfo = json_decode($summoner->summoner_info);
}
return response()->json([
'summonerInfo' => $summonerInfo,
], 201);
}
And then I return a JSON response to my front-end with the summoner info. This all works fine and dandy as long as a user with that summoner name exists. If he doesn't exists, the GET request fails so the rest of my function fails and in return I get an error on my front-end.
So I am wondering what am I supposed to do to get a 404 page on the front-end if my back-end GET request doesn't go through? Both on the front and back-end. I assume I need to return some sort of response from the back-end and then based on that response do something on the front-end?
Here's my front-end:
<template>
<div>{{ summonerInfo }}</div>
</template>
<script>
import axios from 'axios'
import router from '../router'
export default {
data(){
return {
summoner: this.$route.params.summonerName,
summonerInfo: '',
}
},
methods: {
user(action){
let trimmedSummoner = this.summoner.replace(/\s+/g, '');
axios.get('/' + action + 'Summoner/' + trimmedSummoner)
.then((response) => {
this.summonerInfo = response.data.summonerInfo
})
.catch(function (error) {
console.log(error);
})
}
},
watch:{
$route (to, from){
this.summoner = this.$route.params.summonerName
this.user('get')
}
},
mounted(){
this.user('get')
}
}
</script>
One poor mans way of doing this would be to wrap your request in a try / catch. This way, when you request fails, you have the opportunity to catch it and redirect. Downside to this method is that it doesn't give you any info on what the status code is (4xx vs 5xx, etc...).
However, a proper solution would be to use Http Interceptors to handle this.
How can you use axios interceptors?
Here is another example using try / catch approach:
https://gist.github.com/fgilio/230ccd514e9381fafa51608fcf137253
They've also got quite a few examples on this within their GitHub Docs:
https://github.com/axios/axios
Interceptor Example:
axios.interceptors.response.use((response) => {
if(response.status === 401) {
alert("You are not authorized");
}
return response;
}, (error) => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data);
}
return Promise.reject(error.message);
});

How can you use axios interceptors?

I have seen axios documentation, but all it says is
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
Also many tutorials only show this code but I am confused what it is used for, can someone please give me simple example to follow.
To talk in simple terms, it is more of a checkpoint for every HTTP action. Every API call that has been made, is passed through this interceptor.
So, why two interceptors?
An API call is made up of two halves, a request, and a response. Since it behaves like a checkpoint, the request and the response have separate interceptors.
Some request interceptor use cases -
Assume you want to check before making a request if your credentials are valid. So, instead of actually making an API call, you can check at the interceptor level that your credentials are valid.
Assume you need to attach a token to every request made, instead of duplicating the token addition logic at every Axios call, you can make an interceptor that attaches a token on every request that is made.
Some response interceptor use cases -
Assume you got a response, and judging by the API responses you want to deduce that the user is logged in. So, in the response interceptor, you can initialize a class that handles the user logged in state and update it accordingly on the response object you received.
Assume you have requested some API with valid API credentials, but you do not have the valid role to access the data. So, you can trigger an alert from the response interceptor saying that the user is not allowed. This way you'll be saved from the unauthorized API error handling that you would have to perform on every Axios request that you made.
Here are some code examples
The request interceptor
One can print the configuration object of axios (if need be) by doing (in this case, by checking the environment variable):
const DEBUG = process.env.NODE_ENV === "development";
axios.interceptors.request.use((config) => {
/** In dev, intercepts request and logs it into console for dev */
if (DEBUG) { console.info("✉️ ", config); }
return config;
}, (error) => {
if (DEBUG) { console.error("✉️ ", error); }
return Promise.reject(error);
});
If one wants to check what headers are being passed/add any more generic headers, it is available in the config.headers object. For example:
axios.interceptors.request.use((config) => {
config.headers.genericKey = "someGenericValue";
return config;
}, (error) => {
return Promise.reject(error);
});
In case it's a GET request, the query parameters being sent can be found in config.params object.
The response interceptor
You can even optionally parse the API response at the interceptor level and pass the parsed response down instead of the original response. It might save you the time of writing the parsing logic again and again in case the API is used in the same way in multiple places. One way to do that is by passing an extra parameter in the api-request and use the same parameter in the response interceptor to perform your action. For example:
//Assume we pass an extra parameter "parse: true"
axios.get("/city-list", { parse: true });
Once, in the response interceptor, we can use it like:
axios.interceptors.response.use((response) => {
if (response.config.parse) {
//perform the manipulation here and change the response object
}
return response;
}, (error) => {
return Promise.reject(error.message);
});
So, in this case, whenever there is a parse object in response.config, the manipulation is done, for the rest of the cases, it'll work as-is.
You can even view the arriving HTTP codes and then make the decision. For example:
axios.interceptors.response.use((response) => {
if(response.status === 401) {
alert("You are not authorized");
}
return response;
}, (error) => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data);
}
return Promise.reject(error.message);
});
You can use this code for example, if you want to catch the time that takes from the moment that the request was sent until the moment you received the response:
const axios = require("axios");
(async () => {
axios.interceptors.request.use(
function (req) {
req.time = { startTime: new Date() };
return req;
},
(err) => {
return Promise.reject(err);
}
);
axios.interceptors.response.use(
function (res) {
res.config.time.endTime = new Date();
res.duration =
res.config.time.endTime - res.config.time.startTime;
return res;
},
(err) => {
return Promise.reject(err);
}
);
axios
.get("http://localhost:3000")
.then((res) => {
console.log(res.duration)
})
.catch((err) => {
console.log(err);
});
})();
It is like a middle-ware, basically it is added on any request (be it GET, POST, PUT, DELETE) or on any response (the response you get from the server).
It is often used for cases where authorisation is involved.
Have a look at this: Axios interceptors and asynchronous login
Here is another article about this, with a different example: https://medium.com/#danielalvidrez/handling-error-responses-with-grace-b6fd3c5886f0
So the gist of one of the examples is that you could use interceptor to detect if your authorisation token is expired ( if you get 403 for example ) and to redirect the page.
I will give you more practical use-case which I used in my real world projects. I usually use, request interceptor for token related staff (accessToken, refreshToken), e.g., whether token is not expired, if so, then update it with refreshToken and hold all other calls until it resolves. But what I like most is axios response interceptors where you can put your apps global error handling logic like below:
httpClient.interceptors.response.use(
(response: AxiosResponse) => {
// Any status code that lie within the range of 2xx cause this function to trigger
return response.data;
},
(err: AxiosError) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
const status = err.response?.status || 500;
// we can handle global errors here
switch (status) {
// authentication (token related issues)
case 401: {
return Promise.reject(new APIError(err.message, 409));
}
// forbidden (permission related issues)
case 403: {
return Promise.reject(new APIError(err.message, 409));
}
// bad request
case 400: {
return Promise.reject(new APIError(err.message, 400));
}
// not found
case 404: {
return Promise.reject(new APIError(err.message, 404));
}
// conflict
case 409: {
return Promise.reject(new APIError(err.message, 409));
}
// unprocessable
case 422: {
return Promise.reject(new APIError(err.message, 422));
}
// generic api error (server related) unexpected
default: {
return Promise.reject(new APIError(err.message, 500));
}
}
}
);
How about this. You create a new Axios instance and attach an interceptor to it. Then you can use that interceptor anywhere in your app
export const axiosAuth = axios.create()
//we intercept every requests
axiosAuth.interceptors.request.use(async function(config){
//anything you want to attach to the requests such as token
return config;
}, error => {
return Promise.reject(error)
})
//we intercept every response
axiosAuth.interceptors.request.use(async function(config){
return config;
}, error => {
//check for authentication or anything like that
return Promise.reject(error)
})
Then you use axiosAuth the same way you use axios
This is the way I used to do in my project. The code snippet refers how to use access and refresh token in the axios interceptors and will help to implements refresh token functionalities.
const API_URL =
process.env.NODE_ENV === 'development'
? 'http://localhost:8080/admin/api'
: '/admin-app/admin/api';
const Service = axios.create({
baseURL: API_URL,
headers: {
Accept: 'application/json',
},
});
Service.interceptors.request.use(
config => {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
config.headers.common = { Authorization: `Bearer ${accessToken}` };
}
return config;
},
error => {
Promise.reject(error.response || error.message);
}
);
Service.interceptors.response.use(
response => {
return response;
},
error => {
let originalRequest = error.config;
let refreshToken = localStorage.getItem('refreshToken');
const username = EmailDecoder(); // decode email from jwt token subject
if (
refreshToken &&
error.response.status === 403 &&
!originalRequest._retry &&
username
) {
originalRequest._retry = true;
return axios
.post(`${API_URL}/authentication/refresh`, {
refreshToken: refreshToken,
username,
})
.then(res => {
if (res.status === 200) {
localStorage.setItem(
'accessToken',
res.data.accessToken
);
localStorage.setItem(
'refreshToken',
res.data.refreshToken
);
originalRequest.headers[
'Authorization'
] = `Bearer ${res.data.accessToken}`;
return axios(originalRequest);
}
})
.catch(() => {
localStorage.clear();
location.reload();
});
}
return Promise.reject(error.response || error.message);
}
);
export default Service;
I have implemented in the following way
httpConfig.js
import axios from 'axios'
import { baseURL } from '../utils/config'
import { SetupInterceptors } from './SetupInterceptors'
const http = axios.create({
baseURL: baseURL
})
SetupInterceptors(http)
export default http
SetupInterceptors.js
import { baseURL } from '../utils/config'
export const SetupInterceptors = http => {
http.interceptors.request.use(
config => {
config.headers['token'] = `${localStorage.getItem('token')}`
config.headers['content-type'] = 'application/json'
return config
},
error => {
return Promise.reject(error)
}
)
http.interceptors.response.use(function(response) {
return response
}, function (error) {
const status = error?.response?.status || 0
const resBaseURL = error?.response?.config?.baseURL
if (resBaseURL === baseURL && status === 401) {
if (localStorage.getItem('token')) {
localStorage.clear()
window.location.assign('/')
return Promise.reject(error)
} else {
return Promise.reject(error)
}
}
return Promise.reject(error)
})
}
export default SetupInterceptors
Reference : link

Categories

Resources