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
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.