Next.js Server-Side Rendering: Error Handling, Debugging, Logging, and Unit Testing (with TypeScript)

Ram Kumar

Ram Kumar

February 11, 20252 min read

Next.js Server-Side Rendering: Error Handling, Debugging, Logging, and Unit Testing (with TypeScript)

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!

Previous: Deploying an Angular Authentication App with Kubernetes