Image optimization for Next.js with imgproxy

  • tutorial
The power of React in the palm of my hand

Running imgproxy

docker run --rm -p 8080:8080 -it ghcr.io/imgproxy/imgproxy:latest

Using the next/image component

# With npm
npm install @imgproxy/imgproxy-js-core
# With yarn
yarn add @imgproxy/imgproxy-js-core
# With pnpm
pnpm add @imgproxy/imgproxy-js-core
import { type ImageLoaderProps } from "next/image";
import { generateUrl } from "@imgproxy/imgproxy-js-core";

// The address of your imgproxy server
const imgproxyEndpoint = process.env.NEXT_PUBLIC_IMGPROXY_ENDPOINT || "http://localhost:8080";
// The address of your Next.js server.
// This is used to resolve relative image URLs.
const imgproxyBaseUrl = process.env.NEXT_PUBLIC_IMGPROXY_BASE_URL || "http://host.docker.internal:8100";

export default ({ src, width, quality }: ImageLoaderProps) => {
  const fullSrc = new URL(src, imgproxyBaseUrl).toString();

  const path = generateUrl(
    { value: fullSrc, type: "plain" },
    { width, quality },
  );

  return `${imgproxyEndpoint}/unsafe${path}`;
}
Note
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    loader: "custom",
    // Set `loaderFile` to the path of the file with the loader function
    loaderFile: "./src/imgproxyImageLoader.ts",
  },
};

export default nextConfig;
import Image from "next/image";

import TestImage from "@/public/test.jpg";

export default function MyComponent() {
  return (
    <Image
      src={TestImage}
      alt="Test Image"
      width={180}
    />
  );
}
import Image from "next/image";
import imgproxyImageLoader from "@/src/imgproxyImageLoader";

import TestImage from "@/public/test.jpg";

export default function MyComponent() {
  return (
    <Image
      src={TestImage}
      alt="Test Image"
      width={180}
      loader={imgproxyImageLoader}
    />
  );
}

Automatic serving of modern image formats

docker run --rm \
  -p 8080:8080 \
  -e IMGPROXY_AUTO_WEBP=true \
  -e IMGPROXY_AUTO_AVIF=true \
  -it ghcr.io/imgproxy/imgproxy:latest
Important

Security measures

Restricting image sources

docker run --rm \
  -p 8080:8080 \
  -e IMGPROXY_AUTO_WEBP=true \
  -e IMGPROXY_AUTO_AVIF=true \
  -e IMGPROXY_ALLOWED_SOURCES="http://host.docker.internal:8100/,https://images.unsplash.com/" \
  -it ghcr.io/imgproxy/imgproxy:latest

Restricting processing options

docker run --rm \
  -p 8080:8080 \
  -e IMGPROXY_AUTO_WEBP=true \
  -e IMGPROXY_AUTO_AVIF=true \
  -e IMGPROXY_ALLOWED_SOURCES="http://host.docker.internal:8100/,https://images.unsplash.com/" \
  -e IMGPROXY_MAX_RESULT_DIMENSION=2000 \
  -e IMGPROXY_ALLOWED_PROCESSING_OPTIONS="w,q" \
  -it ghcr.io/imgproxy/imgproxy:latest
Tip

Using imgproxy in the presets-only mode

docker run --rm \
  -p 8080:8080 \
  -e IMGPROXY_AUTO_WEBP=true \
  -e IMGPROXY_AUTO_AVIF=true \
  -e IMGPROXY_ALLOWED_SOURCES="http://host.docker.internal:8100/,https://images.unsplash.com/" \
  -e IMGPROXY_ONLY_PRESETS=true \
  -e IMGPROXY_PRESETS="w_200=w:200,w_400=w:400,w_600=w:600,w_800=w:800,q_1=q:1,q_40=q:40,q_80=q:80" \
  -it ghcr.io/imgproxy/imgproxy:latest
import { type ImageLoaderProps } from "next/image";
import { generateUrl } from "@imgproxy/imgproxy-js-core";

// The address of your imgproxy server
const imgproxyEndpoint = process.env.NEXT_PUBLIC_IMGPROXY_ENDPOINT || "http://localhost:8080";
// The address of your Next.js server.
// This is used to resolve relative image URLs.
const imgproxyBaseUrl = process.env.NEXT_PUBLIC_IMGPROXY_BASE_URL || "http://host.docker.internal:8100";

type Presets = Record<string, number>;

const presetsWidth: Presets = {
  w_200: 200,
  w_400: 400,
  w_600: 600,
  w_800: 800,
}

const presetsQuality: Presets = {
  q_1: 1,
  q_40: 40,
  q_80: 80,
}

const findPreset = (presets: Presets, value: number): string | undefined => {
  const sorted = Object.entries(presets).sort(([, a], [, b]) => a - b);
  return sorted.find(([, v]) => v >= value)?.[0] || sorted[sorted.length - 1]?.[0];
};

export default ({ src, width, quality }: ImageLoaderProps) => {
  const fullSrc = new URL(src, imgproxyBaseUrl).toString();

  const presets = [
    findPreset(presetsWidth, width),
    quality ? findPreset(presetsQuality, quality) : undefined,
  ].filter((p) => p !== undefined);

  const path = generateUrl(
    { value: fullSrc, type: "plain" },
    { preset: presets },
    { onlyPresets: true },
  );

  return `${imgproxyEndpoint}/unsafe${path}`;
}

Using a custom server component

# With npm
npm install @imgproxy/imgproxy-node
# With yarn
yarn add @imgproxy/imgproxy-node
# With pnpm
pnpm add @imgproxy/imgproxy-node
import { StaticImageData } from "next/image";
import { generateImageUrl, type IGenerateImageUrl } from "@imgproxy/imgproxy-node";

import styles from "./Imgproxy.module.css";

type Options = NonNullable<IGenerateImageUrl["options"]>;
type Format = Options["format"];

type ImgproxyProps = {
  className?: string;
  src: string | StaticImageData;
  alt?: string;
  fill?: boolean;
} & Omit<Options, "resize" | "size" | "resize_type" | "dpr" | "format">;

// The address of your imgproxy server
const imgproxyEndpoint = process.env.NEXT_PUBLIC_IMGPROXY_ENDPOINT || "http://localhost:8080";
// The address of your Next.js server.
// This is used to resolve relative image URLs.
const imgproxyBaseUrl = process.env.NEXT_PUBLIC_IMGPROXY_BASE_URL || "http://host.docker.internal:8100";

export const Imgproxy = ({
  className,
  src,
  alt,
  width,
  height,
  fill = false,
  ...imgproxyOptions
}: ImgproxyProps) => {
  const resolvedSrc = typeof src === "string" ? src : src.src;
  const fullSrc = new URL(resolvedSrc, imgproxyBaseUrl).toString();

  const imagproxyUrl = (format: Format, dpr: number) => (
    generateImageUrl({
      endpoint: imgproxyEndpoint,
      url: {
        value: fullSrc,
        displayAs: "plain",
      },
      options: {
        resize: {
          width,
          height,
          resizing_type: fill ? "fill-down" : "fit",
        },
        format,
        dpr,
        ...imgproxyOptions
      },
    })
  );

  const srcSet = (format?: Format) => [
    `${imagproxyUrl(format, 1)} 1x`,
    `${imagproxyUrl(format, 2)} 2x`,
  ].join(", ");

  const classNames = [
    className,
    fill ? styles.fill : styles.fit,
  ].filter(Boolean).join(" ");

  return (
    <picture>
      <source srcSet={srcSet("avif")} type="image/avif" />
      <source srcSet={srcSet("webp")} type="image/webp" />
      <img
        src={imagproxyUrl("webp", 2)}
        alt={alt}
        className={classNames}
        width={width || undefined}
        height={height || undefined}
        loading="lazy"
        decoding="async"
      />
    </picture>
  );
};
.fit {
  object-fit: contain;
}

.fill {
  object-fit: cover;
}
IMGPROXY_KEY=736563726574
IMGPROXY_SALT=68656C6C6F
Important
docker run --rm \
  -p 8080:8080 \
  --env-file .env.local \
  -it ghcr.io/imgproxy/imgproxy:latest
import { Imgproxy } from "@/components/Imgproxy";

import TestImage from "@/public/test.jpg";

export default function MyComponent() {
  return (
    <Imgproxy
      src={TestImage}
      alt="Test Image"
      width={500}
      height={400}
    />
  );
}
Resulting image example 1
import { Imgproxy } from "@/components/Imgproxy";

import TestImage from "@/public/test.jpg";

export default function MyComponent() {
  return (
    <Imgproxy
      src={TestImage}
      alt="Test Image"
      width={500}
      height={400}
      fill
    />
  );
}
Resulting image example 2
import { Imgproxy } from "@/components/Imgproxy";

import TestImage from "@/public/test.jpg";

export default function MyComponent() {
  return (
    <Imgproxy
      src={TestImage}
      alt="Test Image"
      width={500}
      height={400}
      fill
      crop={{ width: 0.5, height: 0.5 }}
      gravity={{ type: "sm" }}
    />
  );
}
Resulting image example 3
import { Imgproxy } from "@/components/Imgproxy";

import TestImage from "@/public/test.jpg";

export default function MyComponent() {
  return (
    <Imgproxy
      src={TestImage}
      alt="Test Image"
      width={500}
      height={400}
      fill
      crop={{ width: 0.5, height: 0.5 }}
      gravity={{ type: "sm" }}
      blur={10}
    />
  );
}
Resulting image example 4

Start your free trial today

Or get imgproxy Pro on Cloud Marketplace