Next.js is a powerful React framework that supports server-side rendering (SSR), enabling better SEO and performance. However, dealing with SSR introduces challenges such as error handling, debugging, logging, and testing. This blog post covers best practices for managing errors, debugging SSR applications, implementing logging, and writing unit tests with practical examples using TypeScript.
1. Error Handling in Next.js SSR
Handling Errors in getServerSideProps
Since getServerSideProps runs on the server, errors in this function should be handled properly to prevent breaking the entire page.
Example: Handling API Errors in getServerSideProps
import { GetServerSideProps } from "next";
interface PageProps {
data?: any;
error?: string;
}
export const getServerSideProps: GetServerSideProps<PageProps> = async () => {
try {
const res = await fetch("https://api.example.com/data");
if (!res.ok) {
throw new Error(`API request failed with status ${res.status}`);
}
const data = await res.json();
return { props: { data } };
} catch (error) {
console.error("Error fetching data:", error);
return { props: { error: "Failed to load data" } };
}
};
const Page: React.FC<PageProps> = ({ data, error }) => {
if (error) return <p>{error}</p>;
return <div>{JSON.stringify(data)}</div>;
};
export default Page;
Custom Error Pages
Next.js provides a built-in pages/_error.tsx file to handle server-side errors globally.
Example: Custom Error Page (pages/_error.tsx)
import { NextPageContext } from "next";
interface ErrorProps {
statusCode?: number;
}
const Error = ({ statusCode }: ErrorProps) => {
return (
<p>
{statusCode
? `An error ${statusCode} occurred on the server`
: "An error occurred on the client"}
</p>
);
};
Error.getInitialProps = ({ res, err }: NextPageContext) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;
2. Debugging Next.js SSR Applications
Using console.log and console.error
Logging in getServerSideProps can be useful for debugging API requests or data-fetching issues.
export const getServerSideProps: GetServerSideProps = async () => {
try {
console.log("Fetching data from API");
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return { props: { data } };
} catch (error) {
console.error("Error in getServerSideProps:", error);
return { props: { error: "Data fetch failed" } };
}
};
Using Next.js Debugging Tools
- Enable Debug Mode: Use NODE_ENV=development to get more detailed error messages.
- Inspect Logs in the Terminal: Server-side logs appear in the terminal where Next.js is running.
- Use debug Library: Add granular debugging logs for different parts of the application.
import debug from "debug";
const log = debug("app:server");
log("This is a debug message");
3. Implementing Logging in Next.js SSR
Logging Errors to an External Service
You can use services like Sentry or LogRocket to capture server-side errors.
Example: Sending Errors to Sentry
import * as Sentry from "@sentry/nextjs";
import { GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps = async () => {
try {
const data = await fetch("https://api.example.com/data");
return { props: { data } };
} catch (error) {
Sentry.captureException(error);
return { props: { error: "Error fetching data" } };
}
};
Custom Logger Utility
To standardize logging, create a utility function.
export function logError(error: unknown, context: string): void {
console.error(`[${new Date().toISOString()}] Error in ${context}:`, error);
}
Use it in getServerSideProps:
export const getServerSideProps: GetServerSideProps = async () => {
try {
const data = await fetch("https://api.example.com/data");
return { props: { data } };
} catch (error) {
logError(error, "getServerSideProps");
return { props: { error: "Something went wrong" } };
}
};
4. Unit Testing Next.js SSR Functions
To ensure the reliability of your server-side logic, you can use Jest to write unit tests.
Installing Jest and Testing Library
npm install --save-dev jest @testing-library/react @testing-library/jest-dom ts-jest
Example: Unit Testing getServerSideProps
import { getServerSideProps } from "../pages/index";
jest.mock("node-fetch", () => require("jest-fetch-mock"));
import fetchMock from "jest-fetch-mock";
describe("getServerSideProps", () => {
beforeEach(() => {
fetchMock.resetMocks();
});
it("returns data on successful API call", async () => {
fetchMock.mockResponseOnce(JSON.stringify({ message: "Success" }));
const response = await getServerSideProps({} as any);
expect(response).toEqual({ props: { data: { message: "Success" } } });
});
it("handles API errors correctly", async () => {
fetchMock.mockReject(new Error("API Error"));
const response = await getServerSideProps({} as any);
expect(response).toEqual({ props: { error: "Failed to load data" } });
});
});
Conclusion
Handling errors, debugging, logging, and testing in a Next.js SSR application is crucial for reliability and maintainability. By implementing structured error handling, leveraging debugging tools, using logging services, and writing unit tests, you can build robust server-side applications. Integrating external logging services like Sentry further helps in proactive monitoring and troubleshooting.
Would you like more in-depth examples or explanations on any of these topics? Let me know!