Cloudinary Fetch with Eleventy (Respecting Local Development)

Avatar of Chris Coyier
Chris Coyier on

This is about a wildly specific combination of technologies — Eleventy, the static site generator, with pages with images on them that you ultimately want hosted by Cloudinary — but I just wanna document it as it sounds like a decent amount of people run into this situation.

The deal:

  • Cloudinary has a fetch URL feature, meaning you don’t actually have to learn anything (nice!) to use their service. You have to have an account, but after that you just prefix your images with a Cloudinary URL and then it is Cloudinary that optimizes, resizes, formats, and CDN serves your image. Sweet. It’s not the only service that does this, but it’s a good one.
  • But… the image needs to be on the live public internet. In development, your image URLs probably are not. They’re likely stored locally. So ideally we could keep using local URLs for images in development, and do the Cloudinary fetching on production.

Multiple people have solved this in different ways. I’m going to document how I did it (because I understand it best), but also link up how several other people have done it (which might be smarter, you be the judge).

The goal:

  • In development, images be like /images/image.png
  • In production, images be like https://res.cloudinary.com/css-tricks/image/fetch/w_1200,q_auto,f_auto/https://production-website.com/images/image.png

So if we were to template that (let’s assume Nunjucks here as it’s a nice templating language that Eleventy supports), we get something like this psuedo-code:

<img src="
  {{CLOUDINARY_PREFIX}}{{FULLY_QUALIFIED_PRODUCTION_URL}}{{RELATIVE_IMAGE_URL}}
  "
  alt="Don't screw this up, fam."
/>
DevelopmentProduction
{{CLOUDINARY_PREFIX}}“”“https://res.cloudinary.com/css-tricks/image/fetch/w_1200,q_auto,f_auto/”
{{FULLY_QUALIFIED_PRODUCTION_URL}}“”“https://production-website.com”
{{RELATIVE_IMAGE_URL}}“/images/image.jpg”“/images/image.jpg”

The trick then is getting those… I guess we’ll call them global variables?… set up. It’s probably just those first two. The relative image path you’d likely just write by hand as needed.

Eleventy has some magic available for this. Any *.js file we put in a _data folder will turn into variables we can use in templates. So if we made like /src/_data/sandwiches.js and it was:

module.exports = {
  ham: true
}

In our template, we could use {{sandwiches.ham}} and that would be defined {{true}}.

Because this is JavaScript (Node), that means we have the ability to do some logic based on other variables. In our case, some other global variables will be useful, particularly the process.env variables that Node makes available. A lot of hosts (Netlify, Vercel, etc.) make “environment variables” a thing you can set up in their system, so that process.env has them available when build processes run on their system. We could do that, but that’s rather specific and tied to those hosts. Another way to set a Node global variable is to literally set it on the command line before you run a command, so if you were to do:

SANDWICH="ham" eleventy

Then process.env.SANDWICH would be ham anywhere in your Node JavaScript. Combining all that… let’s say that our production build process sets a variable indicating production, like:

PROD="true" eleventy

But on local development, we’ll run without that global variable. So let’s make use of that information while setting up some global variables to use to construct our image sources. In /src/_data/images.js (full real-world example) we’ll do:

module.exports = {

  imageLocation:
    process.env.PROD === 'true' 
      ? 'https://coding-fonts.css-tricks.com' 
      : '',

  urlPrefix:
    process.env.PROD === 'true'
      ? 'https://res.cloudinary.com/css-tricks/image/fetch/w_1600,q_auto,f_auto/'
      : ''

};

You could also check process.env.CONTEXT === 'deploy-preview' to test for Netlify deploy preview URLs, in case you want to change the logic there one way or the other.

Now in any of our templates, we can use {{images.imageLocation}} and {{images.urlPrefix}} to build out the sources.

<img 
  src="
    {{images.urlPrefixLarge}}{{images.imageLocation}}/image.png
  "
  alt="Useful alternative text."
/>

And there we go. That will be a local/relative source on development, and then on production, it becomes this prefixed and full qualified URL from which Cloudinary’s fetch will work.

Now that it’s on Cloudinary, we can take it a step further. The prefix URL can be adjusted to resize images, meaning that even with just one source image, we can pull off a rather appropriate setup for responsive images. Here’s that setup, which makes multiple prefixes available, so they can be used for the full syntax.

The end result means locally relative image in development:

The multiple versions are a lie in development, but oh well, srcset is kind of a production concern.

…and Cloudinary fetch URLs in production:

Other People’s Ideas

Phil was showing off using Netlify redirects to do this the other day:

https://twitter.com/philhawksworth/status/1328340868726726656

Then the trick to local development is catching the 404’s and redirecting them locally with more redirects.

If hand-crafting your own responsive images syntax is too big of a pain (it is), I highly recommend abstracting it. In Eleventy-land, Nicolas Hoizey has a project: eleventy-plugin-images-responsiver. Eric Portis has one as well, eleventy-respimg, which specifically uses Cloudinary as I have here.

Proving this stuff has really been on people’s minds, Tim Kadlec just blogged “Proxying Cloudinary Requests with Netlify.” He expands on Phil’s tweet, adding some extra performance context and gotchas.