NextJS + Caravaggio, serve images like a rockstar!

NextJS + Caravaggio, serve images like a rockstar!

Original photo by Sebastian Ervi from Pexels

2020/08/23 Edit: The method to get correct Caravaggio parameters has been greatly improved by the introduction of caravaggio-plugin-nextjs which simplifies discovering of current deployment. This means that setting VERCEL_URL into Vercel is not needed anymore. For this to work you need Caravaggio version `3.3.0` at least.

In this guide I'll show you how to get more from your NextJS projects when it's time to serve images. You'll be able to apply any kind of transformation to the images on the fly without the need to create those resources at build time.

What we're going to do is to ask an image proxy to serve the images stored in the public folder and to transform them. Those images will be cached on Vercel CDN after the first request and served at speed of light on any following request. Let's see it in action.

Why and alternative solutions

I had the need to create a srcset for the images in my blog in order to serve webp images to browsers that are capable of reading this format, but also to resize and apply several visual transformations. My blog is powered by nextJS so I need something that integrates with it. There are several solutions and packages out there. What they do is to create static resources based on your request, for example if you want all of your images to be served at different sizes depending on screen size and screen density, these libraries creates all the possible alternative images at build time so that they are available for the final user.
I was a bit unsatisfied by these solutions for a couple of reasons: since images are generated at build time, the build takes longer when the number of images goes up. Also the size of the cache goes up to store all the possible variants of the images. Again, some of these libraries have limitations such as being able to apply only some of the operation (resize the image and produce webp, but not both at the same time).

Being the creator of Caravaggio, I was wondering if there's any way to include it in a NextJS project. Caravaggio is already able to deploy on Vercel, can produce cachable resources (CDN) and it's versatile enough. I can apply lot of transformations at the same time to a single image. In several old projects, which are in production now and serve lots of users, Caravaggio is serving all the assets without a crash in years: it is a good candidate.

Luckily enough, it proved to be very easy to integrate Caravaggio with NextJS.

One great advantage of this approach is that you can use Caravaggio to transform any image, not only the ones in your NextJS public folder.
This will be very useful if you have user contributed images or external resources because you can use the same code in both cases. Caravaggio will consider relative urls to be internal images, while absolute urls will be considered external.

Also, you can apply the same method to transform images that you want to use with css or for css-in-js, thanks to a React hook provided by the library.

Let’s get our hands dirty

All the examples below are written in Typescript because both caravaggio and caravaggio-react are written in typescript. You can ignore TS specific parts if you use plain javascript.

Let's start by installing the needed dependencies in your NextJS project

yarn add caravaggio caravaggio-react caravaggio-plugin-nextjs

// or using npm
npm install caravaggio caravaggio-react caravaggio-plugin-nextjs

Caravaggio is the image proxy itself, while caravaggio-react is a react library that will make your life easier. It contains components and hooks to easily interact with Caravaggio from the code itself. caravaggio-plugin-nextjs seamlessy integrates Caravaggio with NextJS.

Once installed we need to create an api

touch pages/api/assets/[...params].ts

I called the api "assets" but you can change its name to whatever you prefer. This api will serve images by proxying them through Caravaggio. Here the content of the file

// pages/api/assets/[...params].ts

import caravaggio from 'caravaggio';
import nextPlugin from 'caravaggio-plugin-nextjs';

const ONE_DAY = 60 * 60 * 24;

export default caravaggio({
  logger: {
    options: {
      level: "error",
    },
  },
  basePath: "/api/assets",
  browserCache: `s-maxage=${ONE_DAY}`,
  plugins: {
    plugins: [{
      name: 'nextjs',
      instance: nextPlugin()
    }]
  }
});

Even if Caravaggio can act as a complete webserver, it can be used also as a request handler, like in this case.
Let's see what the various configurations do.

logger is just setting the log level to error since we are not interested in more. Logs will be available on your vercel logs.
basePath tells Caravaggio that it's not served from the root folder. If you named your api differently, you must change this value accordingly.
browserCache instruct Caravaggio about the cache value for served images. This value will enable cache on Vercel and the second time the same resource is requested, it will hit vercel cache. You can change the time from one day to any value you prefer.
plugins adds caravaggio-plugin-next. This will transform relative urls to absolute urls, seamlessy integrating Caravaggio with NextJS. You probably don't need to pass any further configuration to the plugin but if you have any particular need, refer to its documentation

That's all, the entire api endpoint is here. Let's see what else to do.

It's time to use Caravaggio in our project. Open you pages/_app.tsx file and have a content similar to this one

// pages/_app.tsx  (or .js)

import { AppProps } from "next/app";
import { CaravaggioProvider } from "caravaggio-react";

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <CaravaggioProvider url="/api/assets">
      <Component {...pageProps} />
    </CaravaggioProvider>
  );
}

Let's see what we have done. We imported a Caravaggio provider through

import { CaravaggioProvider } from "caravaggio-react";

We wrapped the Component with it and passed url option which tells what subpath Caravaggio is served from. In our case it's the internal api we created before. If you choose something different from api/assets, change this value accordingly.

Obtaining fancier images!

It's time to show images in our website. We'll import an Image component from caravaggio-react. It takes the same props as an img tag but also accepts all the operations available on Caravaggio. Also, we don't need to put any strange url in it, we can use the relative path to public resource as we're used to do with NextJS.

Let's say we have an image and we want to serve it in progressive jpg, resized to 300px keeping the aspect ratio. If we have our <img src="/school.png" alt="A nice school building" />, we can write instead:

import { Image } from 'caravaggio-react';

<Image
  src="/school.png"
  alt="A nice school building"
  opt={{
    o: "jpg",
    progressive: true,
    rs: {
      s: "300x",
    },
  }}
/>

The possibility you have with those opt are a lot and you can find any available operation on Caravaggio documentation.

This specific image is not created at build time, but it's generated dynamically on the fly and cached. Build time remains the same as before, no cache folder is used and if you change any of these options there's nothing to regenerate. Fast and easy!

If you want to use Caravaggio to transform an external image, you can use the same component. Caravaggio will consider absolute urls as external resources.

<Image
  src="https://pexels.com/cangaroo.png"
  alt="A cangaroo jumping around"
  opt={{
    o: "webp",
    q: 90,
    blur: 10,
    rs: {
      s: "640x480",
    },
  }}
/>

Easily create srcset from your images

caravaggio-react has a useful component to create srcset easily: ImageSet. Let's see it in action:

import { ImageSet } from "caravaggio-react";

<ImageSet
  src="/myimage.png"
  alt="example image"
  className="myclass"
  // Sets is an array of sources
  sets={[
    {
      // This source produce a webp image
      type: "image/webp",
      // The rules for this source
      rules: {
        // When screen is less wider than 300px
        "300w": {
          // Use this caravaggio options to produce the image
          opt: {
            o: "webp",
            rs: {
              s: "300x",
            },
          },
        },
        // Use this for screen large up to 600px
        "600w": {
          opt: {
            o: "webp",
            rs: {
              s: "600x",
            },
          },
        },
      },
    },
    // This is the second set, we want it for browsers 
    // not capable of handling webp images.
    // The rules are the same except for the output format
    {
      rules: {
        "300w": {
          opt: {
            rs: {
              s: "300x",
            },
          },
        },
        "600w": {
          opt: {
            rs: {
              s: "600x",
            },
          },
        },
      },
    },
  ]}
/>;

The component generates this html:

<figure class="myclass">
  <picture>
    <source
      type="image/webp"
      srcset="
        /api/assets/o:webp/rs,s:300x?image=http://localhost:3000/myimage.png 300w,
        /api/assets/o:webp/rs,s:600x?image=http://localhost:3000/myimage.png 600w
      "
    />
    <source
      srcset="
        /api/assets/rs,s:300x?image=http://localhost:3000/myimage.png 300w,
        /api/assets/rs,s:600x?image=http://localhost:3000/myimage.png 600w
      "
    />
    <img src="/myimage.png" alt="example image" />
  </picture>
</figure>

Hook: use it to get images for css and css-in-js

Image components are not the only way to create images, you can use a hook to generate image urls to use, for example, in css.

Here an example

import { useCaravaggioImage } from "caravaggio-react";

const Component = () => {
  const image = useCaravaggioImage("/images/bg.png", {
    blur: 0.3,
  });

  return <div style={{ backgroundImage: `url('${image}')` }}>Some content</div>;
};

Conclusions

If you want to see the code in action, this blog is powered by the same concept exposed in this article. You can look at the code on the repository.
Also be sure to check all the transformations enabled by Caravaggio and ask for any new operation... pull requests are welcome of course!

Enjoy your images!

Author

Fabrizio Ruggeri