Skip to content

BitPatty/next-image-s3-imgproxy-loader

Repository files navigation

next/image loader for imgproxy (S3)

This library is a layer on top of the the next/image component, which allows you to load images from an imgproxy instance connected to an S3. With this library, the NextJS server acts as a middleman between the client and the imgproxy instance to perform additional tasks, such as applying signatures and/or guards before loading an image.

If you want to access the imgproxy instance directly from your client you can simply use the next/image component itself - no need to install this library (you might wanna look into the imgproxy-url-builder to build the request URL however).

Request Flow

Sample Usage

You can find additional examples in the demo project.

Installation

You can install the package via npm:

npm install --save @bitpatty/next-image-s3-imgproxy-loader

Registering the endpoint

Method 1: Custom Server

The library by default proxies request through a custom endpoint. To register the endpoint create a custom server in your project and add the following lines:

// server.js
const imgProxy = require('@bitpatty/next-image-s3-imgproxy-loader');

app.prepare().then(() => {
  createServer((req, res) => {
    const parsedUrl = parse(req.url, true);
    const { pathname, query } = parsedUrl;

    if (pathname === imgProxy.IMGPROXY_ENDPOINT) {
      // Add other middleware here, such as auth guards
      // ...
      imgProxy.handle(
        new URL('<url to your imgproxy instance>'),
        query,
        res,
        // (Optional) Additional configuration options
        {
          // (Optional) If your imgproxy uses signatures, specify
          // the key and salt here
          signature: {
            // (Required) The IMGPROXY_KEY (hex encoded)
            key: '<imgproxy secret>',
            // (Required) The IMGPROXY_SALT (hex encoded)
            salt: '<imgproxy salt>',
          },
          // (Optional) If your imgproxy instance uses
          // the IMGPROXY_SECRET, specify the token here
          authToken: '<my-token>',
          // (Optional) If you wanna restrict access to specific
          // buckets add an array of valid bucket names
          bucketWhitelist: ['<my-bucket>'],
          // (Optional) A list of imgproxy headers that should be
          // forwarded through the imgproxy endpoint
          forwardedHeaders: ['<my-header>'],
          // (Optional) An object containing additional request
          // headers that should be sent to the imgproxy endpoint
          requestHeaders: {
            'My-Header': 'My-Value',
            // ...
          },
          // (Optional) The logger configuration. If you want additional
          // debug output you can adjust the log level.
          logging: {
            // (Optional) The logger to use (defaults to console)
            // The logger should implement the signature for
            // for console.debug, console.warn and console.error
            logger: console,
            // (Optional) The log level, must be one of
            // 'debug', 'warn' or 'error' (defaults to 'error')
            level: 'debug',
          },
        },
      );
    } else {
      handle(req, res, parsedUrl);
    }
  }).listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
});

Method 2: API Endpoint

For serverless environments you can use an API endpoint instead of a custom endpoint.

The setup is similar, register your endpoint as follows:

// pages/api/image.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { handle } from '@bitpatty/next-image-s3-imgproxy-loader';

const handler = (req: NextApiRequest, res: NextApiResponse): void => {
  if (req.method !== 'GET') {
    res.statusCode = 405;
    res.send('');
    return;
  }

  handle(new URL('http://localhost:4000/'), req.query, res, {
    // Handler Options
  });
};

export default handler;

With this method, you have to supply the endpoint to the <ProxyImage> component.

Using the component

After registering the endpoint you can use the <ProxyImage /> component as you would with the Image component from next/image, except that you need to provide a file (<bucketname>/<filename>) instead of src and optional proxy params for image transformations/optimizations.

import ProxyImage from '@bitpatty/next-image-s3-imgproxy-loader';
import pb from '@bitpatty/imgproxy-url-builder';

<ProxyImage
  file="mybucket/myfile.png"
  proxyParams={pb().rotate(180).blur(10).build()}
/>;

Note: The layout prop is automatically set to 'fill' if no width is set

Using the raw path

In case using the component is not an option, you can instead use the image path itself, by utilizing the buildProxyImagePath function.

import { buildProxyImagePath } from '@bitpatty/next-image-s3-imgproxy-loader';
import pb from '@bitpatty/imgproxy-url-builder';

const imagePath = buildProxyImagePath('test-bucket/test-image.png', {
  proxyParams: pb().blur(10).build(),
});

<img src={imagePath} />;

or as background image

import { buildProxyImagePath } from '@bitpatty/next-image-s3-imgproxy-loader';
import pb from '@bitpatty/imgproxy-url-builder';

const imagePath = buildProxyImagePath('test-bucket/test-image.png', {
  proxyParams: pb().blur(10).format('jpg').build(),
});

<div
  style={{
    backgroundImage: `url(${imagePath})`,
    backgroundSize: 'cover',
  }}
>
  {/* Content */}
</div>;

Overriding the endpoint

If you use a different endpoint than the one provided by IMGPROXY_ENDPOINT you can override the endpoint used by the component by providing the endpoint property. endpoint can be both a path but also a URL.

<ProxyImage file="mybucket/myfile.png" endpoint="/my-endpoint" />;

// Or
buildProxyImagePath('test-bucket/test-image.png', {
  endpoint: '/my-endpoint',
});

Forwarded Headers

By default, the following imgproxy headers will be forwarded in the response to the client:

[
  "date",
  "expires",
  "content-type",
  "content-length",
  "cache-control",
  "content-disposition",
  "content-dpr"
]

If you want to forward a different set of headers you can use the forwardedHeaders option to specify a custom list of headers. Note that if forwardedHeaders is specified, all headers not specified in the list will be omitted in the response.