├── .nvmrc ├── .gitignore ├── src ├── images │ ├── eleventy.png │ ├── netlify.png │ └── cloudinary.png ├── _includes │ └── default.njk ├── styles.css └── index.md ├── .prettierrc.js ├── netlify.toml ├── .github ├── .kodiak.toml └── workflows │ └── upgrade-dependencies.yml ├── package.json ├── LICENSE ├── README.md └── .eleventy.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /src/images/eleventy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/demo-11ty-netlify-cloudinary/main/src/images/eleventy.png -------------------------------------------------------------------------------- /src/images/netlify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/demo-11ty-netlify-cloudinary/main/src/images/netlify.png -------------------------------------------------------------------------------- /src/images/cloudinary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/demo-11ty-netlify-cloudinary/main/src/images/cloudinary.png -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | semi: true, 4 | singleQuote: true, 5 | tabWidth: 2, 6 | useTabs: false, 7 | trailingComma: "es5" 8 | }; 9 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/responsive/:width/*" 3 | to = "https://res.cloudinary.com/nho/image/fetch/q_auto,f_auto,w_:width,c_limit/https://demo-11ty-netlify-cloudinary.netlify.app/:splat" 4 | status = 200 5 | -------------------------------------------------------------------------------- /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [merge] 4 | automerge_label = 'automerge 🤞' 5 | 6 | # https://kodiakhq.com/docs/recipes#better-merge-messages 7 | [merge.message] 8 | title = "pull_request_title" 9 | body = "pull_request_body" 10 | include_pr_number = true 11 | body_type = "markdown" 12 | strip_html_comments = true 13 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade dependencies 2 | 3 | on: 4 | schedule: 5 | # https://crontab.guru/#0_8_*_*_1 6 | - cron: '0 8 * * 1' 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: upgrade-dependencies 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | upgrade-dependencies: 15 | uses: nhoizey/upgrade-dependencies/.github/workflows/upgrade-dependencies.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /src/_includes/default.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 10 | 11 |
12 |

{{ title }}

13 | {{ content | safe }} 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | background-color: cadetblue; 10 | } 11 | 12 | main { 13 | background-color: white; 14 | width: 90vw; 15 | max-width: 42rem; 16 | margin: 1rem auto; 17 | padding: 1rem; 18 | } 19 | 20 | img { 21 | max-width: 100%; 22 | height: auto; 23 | } 24 | 25 | .logo { 26 | max-width: 50%; 27 | margin-top: 1rem; 28 | margin-bottom: 1rem; 29 | } 30 | 31 | @media (min-width: 30rem) { 32 | .logo { 33 | max-width: 25%; 34 | float: right; 35 | margin-top: 0; 36 | margin-left: 1rem; 37 | } 38 | } 39 | 40 | pre { 41 | overflow-x: scroll; 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-11ty-netlify-cloudinary", 3 | "version": "1.0.0", 4 | "description": "A demonstration of responsive images in simple Markdown with Eleventy, Netlify and Cloudinary", 5 | "scripts": { 6 | "start": "NODE_ENV=development eleventy --serve", 7 | "build": "NODE_ENV=production eleventy" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/nhoizey/demo-11ty-netlify-cloudinary.git" 12 | }, 13 | "author": "Nicolas Hoizey (https://nicolas-hoizey.com/)", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/nhoizey/demo-11ty-netlify-cloudinary/issues" 17 | }, 18 | "homepage": "https://github.com/nhoizey/demo-11ty-netlify-cloudinary#readme", 19 | "devDependencies": { 20 | "@11ty/eleventy": "^0.11.1", 21 | "eleventy-plugin-images-responsiver": "~1.8.2", 22 | "image-size": "^0.9.7", 23 | "markdown-it-attrs": "~3.0.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nicolas Hoizey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # demo-11ty-netlify-cloudinary 2 | 3 | This is a demonstration of how to get an optimized performance with responsive images while letting content author writing simple Markdown. 4 | 5 | It uses: 6 | 7 | - [Eleventy](https://11ty.dev/) as Static Site Generator, with my responsive images plugin [eleventy-plugin-images-responsiver](https://nhoizey.github.io/images-responsiver/eleventy-plugin-images-responsiver/) 8 | - [Cloudinary](https://nho.io/cloudinary-signup) to resize and optimize images, 9 | - and [Netlify](https://netlify.com/) for building and hosting the site, and proxying requests to Cloudinary, as shown by [Phil Hawksworth](https://twitter.com/philhawksworth/status/1328340868726726656) and [Tim Kadlec](https://timkadlec.com/remembers/2020-11-17-netlify-proxy-requests/). 10 | 11 | After configuring the plugin, I "just" had to write this in the Markdown content: 12 | 13 | ```markdown 14 | ![Eleventy](/images/eleventy.png){.logo} 15 | ``` 16 | 17 | To get this responsive image in the HTML: 18 | 19 | ```html 20 | 37 | ``` 38 | -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | module.exports = function (eleventyConfig) { 2 | 3 | // Make simple images responsive for production 4 | if (process.env.NODE_ENV === 'production') { 5 | const imagesResponsiver = require('eleventy-plugin-images-responsiver'); 6 | const imageSize = require('image-size'); 7 | const site = 'https://demo-11ty-netlify-cloudinary.netlify.app'; 8 | eleventyConfig.addPlugin(imagesResponsiver, { 9 | default: { 10 | selector: 'img', 11 | resizedImageUrl: (src, width) => src.replace(site, `${site}/responsive/${width}`), 12 | runBefore: (image, document) => { 13 | // A hook that is run before the transformation 14 | 15 | // Get current image's src value 16 | let imageSrc = image.getAttribute('src'); 17 | 18 | // Compute the image's dimensions and add them to the HTML 19 | // to prevent layout shift and help compute srcset values 20 | let imageDimensions = imageSize('./src' + imageSrc); 21 | image.setAttribute('width', imageDimensions.width); 22 | image.setAttribute('height', imageDimensions.height); 23 | 24 | image.setAttribute('src', `${site}${imageSrc}`); 25 | 26 | // Get the class value and set it as a preset for Images Responsiver 27 | image.dataset.responsiver = image.className; 28 | }, 29 | }, 30 | logo: { 31 | fallbackWidth: 320, 32 | minWidth: 90, // width on a 240px viewport with 1dppx density 33 | maxWidth: 400, // width on a 479px viewport width 2dppx density 34 | sizes: '(min-width: 47rem) 10rem, (min-width: 30rem) calc((90vw - 1rem) / 4), calc((90vw - 1rem) / 2)', 35 | attributes: { 36 | loading: 'lazy' 37 | }, 38 | } 39 | }); 40 | } 41 | 42 | // Add attributes support to Markdown-it 43 | const markdownIt = require("markdown-it"); 44 | const markdownItAttributes = require('markdown-it-attrs'); 45 | const markdownLib = markdownIt().use(markdownItAttributes); 46 | eleventyConfig.setLibrary("md", markdownLib); 47 | 48 | eleventyConfig 49 | .addPassthroughCopy('src/images') 50 | .addPassthroughCopy('src/styles.css'); 51 | 52 | return { 53 | passthroughFileCopy: true, 54 | dir: { 55 | output: 'dist', 56 | input: 'src' 57 | }, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default.njk 3 | title: Welcome to this demo! 4 | --- 5 | 6 | This is a demonstration of how to get an optimized performance with responsive images while letting content author writing simple Markdown. 7 | 8 | ## Eleventy 9 | 10 | ![Eleventy logo](/images/eleventy.png){.logo} 11 | 12 | This site is generated by [Eleventy](https://11ty.dev/). 13 | 14 | It uses [eleventy-plugin-images-responsiver](https://nhoizey.github.io/images-responsiver/eleventy-plugin-images-responsiver/), a plugin for Eleventy, to generate responsive image syntax with `srcset` and `sizes` attributes, so that loaded images are not too large (and heavy) for the area of the page they are supposed to fill. 15 | 16 | ## Cloudinary 17 | 18 | ![Cloudinary logo](/images/cloudinary.png){.logo} 19 | 20 | The configuration of the plugin makes it generate multiple versions of the original images thanks to [Cloudinary](https://nho.io/cloudinary-signup)'s Fetch API and [URL based transformations](https://cloudinary.com/documentation/image_transformations). 21 | 22 | Any other image resizing tool (SaaS or not) could be used with adapted configuration. 23 | 24 | ## Netlify 25 | 26 | ![Netlify logo](/images/netlify.png){.logo} 27 | 28 | The site is built and hosted by [Netlify](https://netlify.com/). 29 | 30 | The source code is managed [on GitHub](https://github.com/nhoizey/demo-11ty-netlify-cloudinary), and any push to the `main` branch triggers a build and deploy on Netlify. 31 | 32 | Netlify is also convenient here to [proxy requests](https://docs.netlify.com/routing/redirects/rewrites-proxies/#proxy-to-another-service) to Cloudinary, so that a single domain is "seen" by the browser, which is better for performance. Thanks [Phil Hawksworth](https://twitter.com/philhawksworth/status/1328340868726726656) and [Tim Kadlec](https://timkadlec.com/remembers/2020-11-17-netlify-proxy-requests/) for this nice trick! 👍 33 | 34 | ## What's the point of all this? 35 | 36 | After developers have configured the plugin, content authors "just" had to write this in the Markdown content: 37 | 38 | ```markdown 39 | ![Eleventy](/images/eleventy.png){.logo} 40 | ``` 41 | 42 | To get this responsive image in the HTML: 43 | 44 | ```html 45 | 62 | ``` 63 | 64 | Ok, here I am both the developer and the content writer… 😅 65 | 66 | But even if I'm a developer, I like to use plain simple Markdown for images, so that: 67 | 68 | - it's much simpler to write, 69 | - they show up in my Markdown editor, 70 | - and they also show up on GitHub (exemple [here](https://github.com/nhoizey/nicolas-hoizey.com/blob/master/src/articles/2020/10/26/enhancing-archives-navigation-step-1/index.md)). 71 | --------------------------------------------------------------------------------