next.js: react-pagination (and possibly other libs) render incorrectly on PROD only

Link to the code that reproduces this issue

https://github.com/andreiciceu/nextjs-react-paginate-bug

To Reproduce

  1. setup
yarn install
yarn build
yarn start
  1. open page

Current vs. Expected behavior

Current (13.5.4): image browser console:

(react-paginate): The pageCount prop value provided is not an integer (undefined). Did you forget a Math.ceil()?

Expected (13.5.1): image

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

OS: MacOS,Alpine Linux (doesn't seem to be OS related)
Node: v16.19.0
Yarn: 1.22.19

Which area(s) are affected? (Select all that apply)

Not sure

Additional context

This is visible when running the production build only. DEV works ok. Issue present on both Pages and App Router, and with or without dynamic (no SSR rendering). Downgrading to 13.5.1 solves the problem

About this issue

  • Original URL
  • State: closed
  • Created 9 months ago
  • Reactions: 19
  • Comments: 43 (1 by maintainers)

Most upvoted comments

It has something to do with the SWC minifier. If you set keep_fnames to false in packages/next/src/build/webpack/plugins/terser-webpack-plugin/src/index.ts then this problem goes away.

keep_fnames (default false) – Pass true to not mangle function names. Pass a regular expression to only keep function names matching that regex. Useful for code relying on Function.prototype.name.

So this is why it works in dev, but not prod.

Workaround You can add swcMinify: false to your next.config.js file. (I’m not sure if you can pass options to the SWC minifier directly, but that would be nice)

Same issue. Tried to hardcode pageCount, but didn’t help. Had to downgrade to 13.5.3

We basically rewrote our own and didn’t have the worst time. Bonus is no dependencies.

import { useCallback, useMemo, useState } from "react";

interface Props {
  previousLabel?: string;
  nextLabel?: string;
  pageCount: number;
  onPageChange: (...args: any) => void;
  pageRangeDisplayed?: number;
  breakLabel?: string;
  pageLinkClassName?: string;
  previousClassName?: string;
  nextClassName?: string;
  disabledClassName?: string;
  breakClassName?: string;
  containerClassName?: string;
  activeClassName?: string;
  gap?: Record<string, string>;
  forcePage?: number;
}

const CustomPagination = ({
  previousLabel = "Prev",
  nextLabel = "Next",
  pageCount,
  onPageChange,
  pageRangeDisplayed = 2,
  breakLabel = "...",
  pageLinkClassName,
  previousClassName,
  nextClassName,
  disabledClassName,
  breakClassName,
  containerClassName,
  activeClassName,
  gap = { marginRight: "4px" },
  forcePage,
}: Props) => {
  const [currentPage, setCurrentPage] = useState(forcePage || 0);

  const handlePageClick = useCallback(
    (page: number) => {
      onPageChange({ selected: page });
      setCurrentPage(page);
    },
    [onPageChange],
  );

  const pages = useMemo(() => {
    const pages = [];
    const leftSide = pageRangeDisplayed / 2;
    const rightSide = pageRangeDisplayed - leftSide;

    for (let i = 0; i < pageCount; i++) {
      const label = i + 1;
      let className = pageLinkClassName;
      if (i === currentPage) className += ` ${activeClassName}`;

      if (
        i === 0 ||
        i === pageCount - 1 ||
        (i >= currentPage - leftSide && i <= currentPage + rightSide)
      ) {
        pages.push(
          <button key={i} onClick={() => handlePageClick(i)} className={className} style={gap}>
            {label}
          </button>,
        );
      } else if (
        (i < currentPage - leftSide && i === 1) ||
        (i > currentPage + rightSide && i === pageCount - 2)
      ) {
        pages.push(
          <span key={i} className={breakClassName} style={gap}>
            {breakLabel}
          </span>,
        );
      }
    }

    return pages;
  }, [
    currentPage,
    pageCount,
    pageRangeDisplayed,
    pageLinkClassName,
    activeClassName,
    breakClassName,
    breakLabel,
  ]);

  return (
    <div className={containerClassName}>
      <button
        onClick={() => handlePageClick(currentPage - 1)}
        className={
          currentPage === 0 ? `${previousClassName} ${disabledClassName}` : previousClassName
        }
        disabled={currentPage === 0}
        style={gap}
      >
        {previousLabel}
      </button>

      {pages}

      <button
        onClick={() => handlePageClick(currentPage + 1)}
        className={
          currentPage === pageCount - 1 ? `${nextClassName} ${disabledClassName}` : nextClassName
        }
        disabled={currentPage === pageCount - 1}
      >
        {nextLabel}
      </button>
    </div>
  );
};

export default CustomPagination;

Implementation

interface Props {
  pageCount: number;
  currentPage: number;
  pageRangeDisplayed?: number;
  marginPagesDisplayed?: number;
  hrefBuilder?: (pageIndex: number, pageCount: number, selectedPage: number) => void;
  onPageChange: (page: number) => void;
}

const Pagination = ({ pageCount, pageRangeDisplayed, onPageChange, currentPage }: Props) => {
  const handlePageChange = (selectedItem: { selected: number }) => {
    onPageChange(selectedItem.selected);
  };

  return (
    <div className="mx-auto mt-4 pagination-control">
      <CustomPagination
        previousLabel={"Prev"}
        nextLabel={"Next"}
        pageCount={pageCount}
        onPageChange={handlePageChange}
        pageRangeDisplayed={pageRangeDisplayed || 2}
        breakLabel={"..."}
        pageLinkClassName="px-3 py-2 sm:py-1 rounded-md bg-gray-100 dark:bg-gray-600"
        previousClassName="px-2 sm:px-3 py-2 sm:px-2 sm:py-1 rounded-md bg-gray-100 dark:bg-gray-600"
        nextClassName="px-2 py-2 sm:py-1 rounded-md bg-gray-100 dark:bg-gray-600"
        disabledClassName={"cursor-not-allowed disabled-pagination bg-gray-100 text-gray-500"}
        breakClassName={"break-me px-2 py-2 sm:px-1 text-gray-400"}
        containerClassName={"react-paginate text-sm leading-tight"}
        activeClassName={"!bg-black dark:!bg-white text-white dark:text-black"}
        forcePage={currentPage}
      />
    </div>
  );
};

export default Pagination;

Downgrading to 13.5.3 resolved.

"react-paginate": "^8.1.3",

to:

"react-paginate": "8.1.3",

It works for me

Same issue with next 14.0.0 and react-paginate 8.1.5

@Ishfaque-techno I have same issue with this versions. I solve this issue with downgrade react-paginate version to 8.2.0 -> 8.1.3 this is working on both mode dev and prod.

To resolve this issue, downgrade to react-paginate@8.1.3. You can do this by using the following commands:

Using Yarn:

yarn add react-paginate@8.1.3

Or using npm:

npm install react-paginate@8.1.3

Alternatively, you can manually update your package.json file. Change:

"react-paginate": "^8.1.3",

to:

"react-paginate": "8.1.3",

without the ^ symbol. After making this change, run:

npm install

or

yarn install

Note: I’m using "next": "13.5.4" in this configuration.

To solve this problem, use the following versions: “react-paginate”: “8.1.3”, “next”: “13.5.6”,

and delete the symbol next to the version value “^”

Hello, Same issue here.

Downgrading to React Paginate v8.1.3 has hotfixed this for me too.

I’m using Next v14.0.1

@Ishfaque-techno I have same issue with this versions. I solve this issue with downgrade react-paginate version to 8.2.0 -> 8.1.3 this is working on both mode dev and prod.

tqs it worked

Thanks @camcamcamcam , I can confirm that setting swcMinify to false worked for me on Next.js 13.5.5

ok Thanks.

On Wed, Dec 20, 2023 at 1:42 PM Riyaz Hossain @.***> wrote:

“react-paginate”: “^8.1.3”,

to:

“react-paginate”: “8.1.3”,

that works for me.

— Reply to this email directly, view it on GitHub https://github.com/vercel/next.js/issues/56406#issuecomment-1864001826, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXUQFSRYY2ZF4WMK6DOIFWTYKKJGBAVCNFSM6AAAAAA5SMLEE6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNRUGAYDCOBSGY . You are receiving this because you commented.Message ID: <vercel/next. @.***>

This also worked for me, thanks:

update to next 14.0.3 worked for me

I was able to remove the swcMinify: false workaround that I had used previously.

It has something to do with the SWC minifier. If you set keep_fnames to false in packages/next/src/build/webpack/plugins/terser-webpack-plugin/src/index.ts then this problem goes away.

keep_fnames (default false) – Pass true to not mangle function names. Pass a regular expression to only keep function names matching that regex. Useful for code relying on Function.prototype.name.

So this is why it works in dev, but not prod.

Workaround You can add swcMinify: false to your next.config.js file. (I’m not sure if you can pass options to the SWC minifier directly, but that would be nice)

Another lib impacted is npm i simple-crypto-js@legacy (which install v2.5.1) (legacy is needed to support browsers without native crypto module per readme of the lib).

The error that happens in prod is TypeError: can't access property "sqrt", Math is undefined.

Setting swcMinify: false in next 14.0.2 however it warns that in next 15 this option is gone.

I’m also having this issue with "next": "14.0.1", "react-paginate": "8.2.0",. Upgrading "next": "14.0.2" worked for me.

Downgrading to react-paginate@8.1.3 works.

Current Next.js version = 12.3.6

Same issue

next@13.5.6 react-paginate@8.2.0

The issues seems to have started at this commit