I use fetch() api routes to populate my dashboard page with data. During development I adopted a plan to use try {} catch {} blocks to handle and catch errors, and then pass the error message to a local log file. Then the server would respond with {ok:false} to be a universal way to say, not good. It's exactly the same as other conditions, like not being logged in, which makes the production server very opaque.
Here's an example:
import { logError } from 'errorLogger';
import { getEncryptedSessionCookie, smartSessionSave } from 'authHandler';
export async function GET(req) {
try {
const session = await getEncryptedSessionCookie();
// Perform a series of tasks.
await smartSessionSave(session);
return Response.json(ret);
} catch (err) {
logError('/api/happyLittleRoute',err);
return Response.json({ok:false});
}
Anyway, Now that I am testing the Next server in production, I see that when I build the application, I get an error like this:
/api/happyLittleRoute Error: Dynamic server usage: Page couldn't be rendered statically because it used `cookies`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error
I have been able to remove the error by eliminating the try/catch block, which indicates that that is the culprit. However, as a result, I do not get error logging to the file like I would want with the logError() function. Also, without the try/catch block, the server ends up returning server errors, which fails to meet my requirement of the server always look like it's running fine to the outside world.
I have searched around nextjs.org for information on how to create an error handler, but the documentation only describes how to create something for handling errors when rendering a page.
https://nextjs.org/docs/app/api-reference/file-conventions/error
Is there a proper way to create an error boundary for all the GET() and POST() exported functions to behave like the catch block shown above that still builds well and runs in production?
Update:
While I haven't found an answer to the solution above, I have instead eliminated try blocks on the root of route segments, and instead am considering unhandled errors as a design flaw.
One common practice that I have taken up is to returning objects from library functions I write with the object ok to indicate status and data to indicate the return.
myDatabaseCall.js
export async function getUserList(authObj) {
try {
// do all the database stuff here
return {ok: true, data: arrayOfThings}
} catch (err) {
// handle custom logging for production.
return {ok:false}
}
}
route.js
import getUserList from './myDatabaseCall.js';
import performAuthCheck from './myAuthCheck.js'; //not shown
export async function GET(req) {
let auth = await performAuthCheck(req,'administrator');
if (auth.ok === false) {
return {ok:false}
}
let userList = getUserList(auth.data);
if (userList.ok === false) {
return {ok:false}
}
return {ok:true, data:userList.data};
}
It is a little more repetitive, but the response of the backend remains opaque to errors, and it is also easy for the front end to handle how the response is processed.