how to cleanly handle errors in nextjs getStaticProps - javascript

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 } };
}

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.

Is there a way for me to send the 404 page using the Next.js api routes

Basically, I have an API route /api/signin.js. And I want to accept post requests and return the 404 page when I get a get request. I haven't found any way to return the 404 page without doing something like a redirect. Any help would be appreciated. Thank you.
export default async function Signin(req, res) {
// /////////////////////////////////////////////////////// //
// Here is where I want to send the 404 custom 404.js page //
// /////////////////////////////////////////////////////// //
if(req.method === "GET") {
return res.status(404).send();
}
if(req.method !== "POST") {
return res.status(404).send();
}
try {
const { cookies, body } = req;
console.log(req.cookies);
// Signin logic...
} catch (err) {
res.status(500).json({ err: "Internal server error" });
}
}
As a workaround you could use ReactDOMServer's renderToString function
Render a React element to its initial HTML. React will return an HTML string. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes.
or ReactDOMServer's renderToStaticMarkup function
Similar to renderToString, except this doesn’t create extra DOM attributes that React uses internally, such as data-reactroot. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save some bytes.
// pages/404.js
export default function Custom404() {
return <h1>404 - Page Not Found</h1>;
}
// pages/api/signin.js
import ReactDOMServer from "react-dom/server";
import NotFound from "../404"; // import your 404 page
export default function Signin(req, res) {
if (req.method === "GET") {
return res
.status(404)
.send(ReactDOMServer.renderToStaticMarkup(<NotFound />));
}
if (req.method !== "POST") {
return res.status(404).send();
}
try {
const { cookies, body } = req;
console.log(req.cookies);
// Signin logic...
} catch (err) {
res.status(500).json({ err: "Internal server error" });
}
}
This way you can send a 404 status code and send an html representation back without a redirect.

Error handling API calls with axios Interceptors. Am I doing this right?

Hello I'am completly new with React/Redux so there is a possibility that I violated some principles with the below code , so bare with me.
I'm building a React App which will consume my Express API. Everything is working perfectly but when I was building the Action Creators I couldnt think of a good way to handle any errors coming from the API without wrapping every single axios request with try/catch blocks.
Both in PHP world where I come from and Express you can create a global Error handler.
For any async requests in my Express APP I wrap them with the below function so I can catch them the same way as the synchronous.
module.exports = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch((err) => next(err));
};
};
From what I've learned through googling is that, there is an ErrorBoundary HOC for handling errors inside Components and for axios calls I should use axios interceptors. So I created this:
AxiosFactory Class
import axios from "axios";
import { setError } from "../actions/utilActions";
import store from "../store";
class AxiosFactory {
constructor(baseURL) {
this.instance = axios.create({
baseURL,
});
this.instance.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
// Getting the errors from Express API
const {
response: {
data: { errors },
},
} = error;
store.dispatch(setError(errors));
return Promise.reject(error);
}
);
}
getInstance() {
return this.instance;
}
}
export default AxiosFactory;
User API Caller
import AxiosFactory from './AxiosFactory';
const axios = new AxiosFactory('/api/v1/users/').getInstance();
export default axios;
User ActionCreator
import { SUCCESS_LOGIN } from "./types/userTypes";
import userApi from "../apis/user";
// Tries to login the user
export const signInUser = () => {
return async (dispatch) => {
// Test
const {data:{data:{user} = await userApi.post("login", {
email: "test#test.com",
password: "test12345!",
});
dispatch({
type: SUCCESS_LOGIN,
payload: user,
});
}
Error ActionCreator
import { HAS_ERROR } from "./types/utilTypes";
export const setError = (errors) => {
return async (dispatch) => {
dispatch({
type: HAS_ERROR,
payload: errors,
});
};
};
The interceptor dispatches succesfuly the setError and the error state is getting updated like a charm, which means I dont need to manual dispatch on each call. Although I still need to catch the Promise rejection from Interceptor.
My 2 questions are:
Is there a way to lets say "stop the dispatch from executing" inside my User ActionCreator without try/catching the Promise ?
Does this whole thing I setup makes sense ? Or there is a better way to do it?

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

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.

Javascript 404 error when trying to access API call

I am trying to alter some data inside of my database, however I am getting the error once my api request is called:
Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
Along with the corresponding network error of 404. I am not quite sure why it isn't recognnizing my api call, here is the initial fetch call:
import fetch from '../../../../core/fetch/fetch.server';
import history from '../../../../core/history';
export default function checkIn(orderId) {
debugger;
return async (dispatch, getState) => {
// dispatch({ type: BOXOFFICE_CHECKING_IN });
const response = await fetch(`/api/orders/${orderId}/checkIn`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
checkedIn: true,
}),
}
);
if (response.status === 200) {
// dispatch({ type: BOOKING_CHECKED_IN });
} else {
const errorResponse = await response.json();
if (errorResponse.code === 'card_error') {
// dispatch({ type: BOXOFFICE_CHECKED_IN_ERROR });
}
}
} catch (err) {
throw err;
}
};
}
And my api file (removed everything that isn't relevant):
import { Router } from 'express';
import checkIn from '../handlers/api/orders/checkInCustomer';
export default (resources) => {
const router = new Router();
router.post('/orders/:orderId/checkIn', checkIn(resources));
return router;
};
Which ultimately is meant to call my js file that changes the data databse entry:
import { defaultTo } from 'lodash';
import guuid from '../../../../../../core/guuid';
import authenticateAdmin from '../authenticateAdmin';
import order from '../../../../client/reducers/ui/modals/order';
export default ({ knex }) =>
authenticateAdmin(knex)(async (req, res) => {
try {
console.log('checkinCustomer');
const { orderId } = req.params;
const { isCheckedIn } = req.body;
console.log(orderId);
console.log(isCheckedIn);
await knex('orders').where('is_checked_in', '=', orderId).update({ is_checked_in: isCheckedIn }).where({ id: orderId });
res.status(201).end();
} catch (err) {
console.error(err.stack || err);
}
});
Can anyone spot something that is fundamentally wrong in my code, I can assure you the file paths are all correct and all functions are visible to each other, maybe it's the way I have parsed my data?
EDIT
I thought it maybe of use to include that I am also getting a CORS error:
Access to XMLHttpRequest at 'http://localhost:3000/api/orders/c7216fc0-1197-4cb6-99d4-15760f00b6e7/checkIn' from origin 'my site name' has been blocked by CORS policy:
FURTHER EDIT
I have managed to remove the original JSON error, however I am still getting the 404 network error as well as the original CORS error... In addition to this, if I change the last section of the fetch from checkIn to say cancel which is a fully working api call, the same errors persist.
You should not use JSON.stringify() in passing data to your body.
You should pass json format in your body as you are using application/json.
I have a solution! Turns out my import fetch from '../../../../core/fetch/fetch.server';
in the initial file was wrong and should have been import fetch from '../../../../core/fetch/fetch';!

Categories

Resources