Tractor Loader is a webpack loader to help you with your crops and other image adjustments. This loader performs edits to images based on an inline URL syntax. It integrates cleanly with the NextJS optimized image loader and works well with next dev. All transformations are performed at build time and have no impact on runtime performance.

Check the box to view live examples.

Overview

Why?

The NextJS <Image /> documentation recommends using static image imports for local images.

import cat from "./cat.jpg";

This syntax provides a natural location to express image modifications.

import cat from "./cat.jpg?crop=0,300,0,200&tractor";

Installation

Install tractor-loader with your package manager of choice.

npm install tractor-loader

Add it to the image pipeline in next.config.js.

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack: (config, options) => {
    config.module.rules.forEach((rule) => {
      if (rule.loader === "next-image-loader") {
        rule.use = [
          {
            loader: rule.loader,
            options: rule.options,
          },
          {
            loader: "tractor-loader",
          },
        ];
        rule.loader = undefined;
        rule.options = undefined;
      }
    });
    return config;
  },
};

module.exports = nextConfig;

Add a new file tractor.d.ts to satisfy TypeScript.

declare module "*&tractor" {
  // StaticImageData from "next/image";
  const contents: {
    src: string;
    height: number;
    width: number;
    blurDataURL?: string;
    blurWidth?: number;
    blurHeight?: number;
  };
  export = contents;
}

MDX

Tractor loader can be used with @next/mdx using the tractor-loader-mdx remark plugin.

npm install tractor-loader-mdx

Edit the next.config.mjs to include the plugin. The source for this site is an example.

import tractorLoaderMDX from "tractor-loader-mdx";
// ...
const withMDX = createMDX({
  extension: /.mdx?$/,
  options: {
    remarkPlugins: [tractorLoaderMDX],
  },
});
            

Then in your components handler, such as mdx-components.tsx, handle TractorLoaderImage.

import type { MDXComponents } from "mdx/types";
import Image from "next/image";

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    TractorLoaderImage: (props) => {
      return (
        <Image
          {...props}
          alt={props.alt || ""}
          className="my-0"
          sizes="(max-width: 56rem) 50vw, 28rem"
        />
      );
    },
    ...components,
  };
}

Any Tractor Loader syntax in your MDX will be processed. For example:

This is an MDX example:

![img](./cat.jpg?myrotate=145&crop=o-300,o-300,o300,o300&width=200&tractor)
            

This is an MDX example:

Application

Tractor loader can be applied to any static image import by adding a query string ending in &tractor, such as in the following example. Here 400 px is cropped from the top of the image, then it is coerced to a 16:9 aspect ratio.

import cat from "./cat.jpg?crop=400,0,0,0&aspect=16:9&tractor";

The query string is interpreted as a sequence of operations which are applied from left to right. The key of each key-value pair of the query string identifies an operation, and the value is operation-specific configuration.

Conventions

Units

Default units are pixels. If a '%' sign is used, the unit is a percentage of width or height depending on context.

Image Layout

Tractor loader uses an (x, y) coordinate system where the top-left of the image is (0, 0) and the bottom-right of the image is (width, height). The following shorthand applies to image regions.

codenamemeaning
ttopy coordinate 0
bbottomy coordinate equal to image height
lleftx coordinate 0
rrightx coordinate equal to image width
wwidthimage width
hheightimage height

Extension

Tractor loader can be extended by defining new operations or by giving reusable preset names to sequences of operations.

New Operations

New operations must implement a "parse" method that takes the value from the query string and returns a parsed value, and an "apply" method that takes that value, the sharp metadata for the image, and a sharp instance representing the image, and returns a new sharp instance.

export interface Operation {
  parse: (v: string) => any;
  apply: (
    v: any,
    metadata: sharp.Metadata,
    working: sharp.Sharp,
  ) => sharp.Sharp;
}

New operations are registered via loader options. For example, here is a registration for an operation to rotate an image.

// ... in next.config.js webpack configuration ...
{
  loader: "tractor-loader",
  options: {
    plugins: {
      myrotate: {
        parse: (v) => Number(v),
        apply: (v, _, working) => working.rotate(v),
      }
    }
  }
},

A registered operation can be used in the query string together with other operations.

Rotate 145 degrees then crop to a 600 by 600 centered region

Registered operations override built-in operations with the same name.

Presets

Common sequences of operations can be named as presets in the webpack configuration.

// ... in next.config.js webpack configuration ...
{
  loader: "tractor-loader",
  options: {
    presets: {
      mybanner: "crop=20%,0,20%,0&aspect=16:9",
    }
  }
},

Then the preset can be applied in the query string. The preset is expanded to its component operations wherever it appears and can be mixed with other presets and operations.

Apply the mybanner preset

Operations

Aspect

Constrains an image to a specific aspect ratio by adjusting one of the image dimensions while leaving the other unmodified. Performs the minimum adjustment necessary to achieve the desired ratio. See sharp resize for available options.

Syntax

aspect=w:h[;option:value]...
w:h      aspect ratio to apply
options  sharp options

Examples

Apply a 16:9 aspect ratio aligned to the left side

Crop

Crops an image to a target region. This uses a combination of extract and extend.

Syntax

crop=l,t,r,b[,ox,oy][;option:value]...
l,t,r,b   left, top, right, bottom
ox,oy     x and y for the 'o' origin
options   sharp options

Each part is provided as an optional origin, followed by a value, followed by an optional '%' to use percent units. For example, l20% refers to an edge offset 20% of the image width from the left, while b100 refers to an edge offset 100 pixels from the bottom (offset direction is flipped for r and b origins). When an origin is not provided, the default origin for that part is applied.

By default the origin 'o' is the image center point. If ox and oy are provided, they redefine that point.

partvalid originsdefault origin
ll, r, ol
tt, b, ot
rl, r, o, wr
bt, b, o, hb
oxl, rl
oyt, bt

Examples

Crop 400 pixels from the top and bottom.

Crop to a 200 by 150 region offset 20% from the left and 40% from the top.

Crop to a region 90 px greater than the image size in all directions, filling with green.

Crop to a 220 by 110 region offset 56% from the right and 44% from the top.

Height

Resizes an image to a height while preserving aspect ratio. See sharp resize for available options.

Syntax

height=h[;option:value]...
h        height to apply
options  sharp options

Examples

Resize to 100 pixels high

Width

Resizes an image to a width while preserving aspect ratio. See sharp resize for available options.

Syntax

width=w[;option:value]...
w        width to apply
options  sharp options

Examples

Resize to 120 pixels wide with the nearest-neighbor kernel

Image Credits

Cat Photo by Mikhail Vasilyev on Unsplash