javascriptreactjsnode.jsnext.jsweb-applications

Why is my nextjs server action being treated as a client routine?


I'm trying to add infinite scrolling to NextJS's gallery template project by following this tutorial. Here is my server action:

// /actions/getImages.ts

'use server'

import { promises as fs } from 'fs';
import sizeOf from 'image-size';
import type { ImageProps } from "../utils/types";

export const getImages = async (offset: number, 
                                limit: number) => {
    try {
        const file = await fs.readFile('./public/data-large.json', 'utf8');
        const data = JSON.parse(file);

        let reducedResults: ImageProps[] = [];

        let i = offset;
        for (let entry of data) {
            const dim = sizeOf('./public/kitties/kitty-' + entry.public_id + '.jpg');
            reducedResults.push({
                id: i,
                height: dim.height,
                width: dim.width,
                public_id: entry.public_id,
                format: entry.format,
            });

            if (i >= limit) {
                break;
            } else {
                i++
            }
        }

        return reducedResults;
    } catch (error: unknown) {
        console.log(error);
        throw new Error(`An error occurred: ${error}`);
    }
}

And here is my Component:

// /components/Gallery.tsx

'use client'

import Image from "next/image";
import Link from "next/link";
import type { ImageProps } from "../utils/types";
import { useState } from "react";
import { getImages } from '../actions/getImages';

const NUMBER_OF_IMAGES_TO_FETCH = 15; 

export default function Gallery({ images }: { images: ImageProps[] }) {
    const [offset, setOffset] = useState(NUMBER_OF_IMAGES_TO_FETCH);
    const [imgs, setImgs] = useState<ImageProps[]>(images);

    const loadMoreImages = async () => {
        const newImages = await getImages(offset, NUMBER_OF_IMAGES_TO_FETCH);
        setImgs(imgs => [...imgs, ...newImages]);
        setOffset(offset => offset + NUMBER_OF_IMAGES_TO_FETCH);
    }

    return (
      <>
        <div className="columns-1 gap-4 sm:columns-2 xl:columns-3 2xl:columns-4">
          {imgs.map(({ id, public_id, format }) => (
            <Link
              key={id}
              href={`/?photoId=${id}`}
              as={`/p/${id}`}
              //ref={id === Number(lastViewedPhoto) ? lastViewedPhotoRef : null}
              shallow
              className="after:content group relative mb-5 block w-full cursor-zoom-in after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:shadow-highlight"
            >
              <Image
                alt="Image"
                className="transform rounded-lg brightness-90 transition will-change-auto group-hover:brightness-110"
                style={{ transform: "translate3d(0, 0, 0)" }}
                src={`/kitties/kitty-${public_id}.jpg`}
                width={720}
                height={480}
                sizes="(max-width: 640px) 100vw,
                  (max-width: 1280px) 50vw,
                  (max-width: 1536px) 33vw,
                  25vw"
                />
            </Link>
          ))}
        </div>
        <button className="text-white" onClick={loadMoreImages}>Load more</button>
      </>
    )
}

But I am getting the following build error:

 ○ Compiling / ...
 ⨯ ./node_modules/image-size/dist/index.js:4:1
Module not found: Can't resolve 'fs'
  2 | Object.defineProperty(exports, "__esModule", { value: true });
  3 | exports.types = exports.setConcurrency = exports.disableTypes = exports.disableFS = exports.imageSize = void 0;
> 4 | const fs = require("fs");
    | ^
  5 | const path = require("path");
  6 | const queue_1 = require("queue");
  7 | const index_1 = require("./types/index");

https://nextjs.org/docs/messages/module-not-found

Import trace for requested module:
./actions/getImages.ts
./components/Gallery.tsx
./pages/index.tsx

I assume the server action is being incorrectly treated as a client routine, which is why fs can't be found. But I'm not sure why that is the case because I'm including 'use server' directive in that file. I can't tell what what I'm missing.

Version info:

$ node -v
v20.18.0

$ npx next --version
Next.js v15.0.3

Solution

  • Answering my own question: I wasn't paying attention to which router I was using (pages vs app router). I've converted my project from the pages router (which does not support server actions) to the app router.