reactjsimagenext.jsupload400-bad-request

next.js 400 bad request after trying to access just uploaded image


If I upload an image via form and then want to grab it via next.js api and display via Image component I have 400 Bad Request response. Image component says "Could not load the image". Tried to specify domain in config and use Loader, the same result. More than that, I can't access new uploaded images directly via browser url bar. But if I rebuild the project those previously uploaded images finally displays and there is no 400 error. I upload images in /public/uploads directory. This problem happens only in production mode (npm run build, npm run start). There is no such problem when I work with it in dev mode locally (npm run dev). How to make freshly uploaded images correctly displays without rebuilding a project?


Solution

  • Summary

    Write a custom api endpoint to serve uploaded content, because Next.JS does not support this natively


    I ran into this problem as well. I was uploading temporary images to public/_i. During development with next run dev everything appeared great, but once I built and ran in prod mode I started seeing:

    ⨯ The requested resource isn't a valid image for /_i/f7e5c4f31c368fc46057fd55bf8c066c.jpeg received text/html; charset=utf-8
    

    I looked into loaders and setting custom headers for that path. None of it worked.

    Turns out, **Next.JS encourages uploading dynamic content to a separate service or cdn. ** They do not encourage dynamically serving image content like this, and it may be considered an anti-pattern. Under the image optimization page they make the case:

    images account for a huge portion of the typical website’s page weight and can have a sizable impact on your website's LCP performance

    This is demonstrated by the fact that your content appeared after you rebuilt. That is because the build system has cached your uploaded image as if it were static content.


    Despite all of this, if you (like me) want to proceed, this is what I did:

    1. During upload, store a key or path to use later
    2. Write an api endpoint to serve images from your upload folder based on your key/path
    import fs from 'fs';
    import path from 'path';
    
    export async function GET(request: Request) {
        if (!process.env.IMAGE_UPLOAD_PUBLIC_PATH) {
            throw new Error('IMAGE_UPLOAD_PUBLIC_PATH is not set');
        }
    
        const url = new URL(request.url);
        const image = url.searchParams.get('key');
    
        if (image?.trim()) {
            const imagePath = path.join('public', process.env.IMAGE_UPLOAD_PUBLIC_PATH, `${image}.jpeg`);
    
            if (fs.existsSync(imagePath)) {
                const buffer = fs.readFileSync(imagePath);
    
                return new Response(buffer, {
                    headers: {
                        'Content-Type': 'image/png',
                    },
                });
            } else {
                return new Response('', { status: 404 });
            }
        }
    
        return new Response('', { status: 400 });
    }
    
    1. Replace any use of <Image /> with a regular <img />
    {/* eslint-disable-next-line @next/next/no-img-element */}
    <img src={`/api/image?key=${image}`} className="h-full w-full" />
    

    And that should be all you need.