Search code examples
javascriptreactjsnext.jsserver-side-rendering

Run async code in nextjs component without using UseEffect


In my nextjs app, I have a StrapiImage component that accepts an image object from my strapi backend api as a prop. It assigns the width, height and url + any aditional props. (It's basically a shortcut for a regular next/image)

import Image from "next/image";
import { getStrapiMedia } from "../../lib/media";

export default function StrapiImage({ image, ...props })
    return <Image src={getStrapiMedia(image)} // the getStrapiMedia function basically just returns the url to the image.
                  width={image.attributes.width}
                  height={image.attributes.height}
                  alt=""
                  {...props} />
}

Now, I wanted to add a blur placeholder for this image using placeholder="blur" but since this is an external image, I have to provided a base64 image in the blurDataUrl prop.

I wanted to generate an image like this with the plaiceholder library similarly to this sollution I found.

There's a problem with this though, to generate the image, I need to use an await statement. In getStaticProps, this wouldn't be a problem as I could just make the function async, but I'm doing this inside a component and components must regular non-async functions.

The 'obvious' sollution would be to use the useEffect hook like so:

import Image from "next/image";
import { getStrapiMedia } from "../../lib/media";
import { useEffect, useState } from "react";
import { getPlaiceholder } from "plaiceholder";

export default function StrapiImage({ image, ...props })
    const [blur64, setBlur64]
    useEffect(() => {
        async function generatePlaceholder() {
            // generate base64 image here
        }
        generatePlaceholder();
    }, [])
    return <Image src={getStrapiMedia(image)}
                  width={image.attributes.width}
                  height={image.attributes.height}
                  alt=""
                  placeholder="blur"
                  blurDataURL={blur64}
                  {...props} />
}

This would technically work, but it would only run only on the client-side, ultimately defeating the purpose of ssr and the whole image optimisation. (Especially considering plaiceholder's huge js bundle)

Is there any other way that I could do this without useEffect but still in the component file? Ideally, I'd like it to run only on the server-side.

I'm using nextjs@13


Solution

  • I fixed the issue by using a strapi plugin to generate the placeholder image on the backend rather than on the frontend.

    The issue in the title still remains unsolved though. Running pre-renderable async code at component-level doesn't seem to be officialy supported by nextjs. (with the pages directory)

    Here's some "sollutions" I've found: (mostly workarounds)

    • Generate the placeholders on page-level. For me, that would mean looping through all elements in my api data looking for images and generating placeholders for them. It's pretty inconvenient but it does work.
    • Switch to the app directory. (provided you're using next 13) It uses react server components so all of this is possible and simple to do there.
    • Try to work with useSSE. I haven't looked into this too much but it looks like you can use the useSSE library to run server-side at component-level. Here's an article covering this.