Open source. Your Cloudflare account.

Your images.
Optimized at the edge.

Resize to WebP. Cache globally. Keep control.
A small, self-hosted image optimizer for Cloudflare and Next.js.

Sharp Workers R2

01 / PLAYGROUND

See the difference.

Pick a width and quality. Compare file sizes.
Run it again to see the cache at work.

GET/imageJPEG WebP
A road winding between red sandstone cliffs
Original photo Photo from Unsplash
Original JPEG
Measuring…
Optimized WebP
Not yet
Size change
Not yet

Start at 1200 px and quality 85, or choose your own settings.

02 / INTEGRATION

Use with Next.js.

Your usual next/image component.
Your own image infrastructure.

Next.js handles responsive sizes and lazy loading. imgt serves the WebP. Add these three files to your app, then restart its development server.

Set it up with your agent.

Copy a prompt to deploy imgt in your own Cloudflare account and connect it to your project.

View the setup prompt
Help me self-host imgt on my Cloudflare account and integrate it into my project.

Read https://imgt.fashn.ai/llms.txt and follow its deployment, configuration and
Next.js guides before making changes. The source is
https://github.com/fashn-AI/imgt. Inspect my project and preserve its existing
configuration. Ask for missing information: my target Cloudflare
account, my own imgt deployment URL and the public HTTPS image sources I use.

1. Set up my own imgt deployment through Cloudflare's GitHub integration.
   Production branch pushes should deploy automatically. Use Wrangler to
   inspect the intended account; do not manually deploy or assume the account
   open in a browser is the correct one.
2. Set ALLOWED_HOSTS to the hosts serving my original images. Include
   images.unsplash.com if I want the playground. Wildcards such as *.example.com
   do not include the parent example.com. Validate redirect hosts too. Follow
   the deployment guide's GitHub rebuild step after dashboard variable updates.
3. Keep the imgt R2 bucket private, bound as DERIVATIVES, using Standard storage.
   Complete storage setup with an enabled lifecycle rule that deletes objects
   under derivatives/ after 30 days. Deployment does not add this rule.
4. If my app uses Next.js, adapt the TypeScript custom image loader to my
   existing configuration. Use my own imgt URL, configure the qualities my
   components use and preserve image dimensions and responsive sizes.
   Otherwise, use the /image API from my project with my own endpoint.
5. Replace every endpoint placeholder with my deployment URL. The FASHN AI
   website is a demonstration, not a shared optimization service for my app.
6. Run the repository checks and test the deployed image pipeline using
   npm run smoke -- MY_IMGT_ENDPOINT MY_PUBLIC_IMAGE_URL. Verify decoded WebP
   output, repeat cache hits and conditional 304 responses. Do not treat
   /health alone as an end-to-end check. Only use --fresh with source URLs that
   accept additional query parameters.

Keep the implementation small, explain the changes and call out any setup
steps I still need to complete in Cloudflare.
  1. Configure the loader.

    List the quality values your Next.js components use.

    next.config.ts
    import type { NextConfig } from "next";
    
    const nextConfig: NextConfig = {
      images: {
        loader: "custom",
        loaderFile: "./image-loader.ts",
        qualities: [77],
      },
    };
    
    export default nextConfig;
  2. Point it at imgt.

    Replace the placeholder with your own deployed imgt URL. Deploy your own instance before integrating; this website provides a playground, not a shared optimization service.

    image-loader.ts
    "use client";
    
    import type { ImageLoaderProps } from "next/image";
    
    export default function imageLoader({
      src,
      width,
      quality = 77,
    }: ImageLoaderProps) {
      const url = new URL("/image", "https://imgt.YOUR-SUBDOMAIN.workers.dev");
      url.search = new URLSearchParams({
        url: src,
        w: String(width),
        q: String(quality),
      }).toString();
      return url.href;
    }
  3. Use your Image.

    Set dimensions and sizes to match your layout. imgt accepts integer widths from 1 to 3840 and qualities from 1 to 100; this example uses 77.

    app/page.tsx
    import Image from "next/image";
    
    export default function Page() {
      return (
        <main style={{ maxWidth: 800, margin: "0 auto" }}>
          <Image
            src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?fm=jpg&fit=crop&w=1920&h=1200&q=90"
            alt="Landscape from Unsplash"
            width={1920}
            height={1200}
            quality={77}
            sizes="(max-width: 768px) 100vw, 800px"
            style={{ width: "100%", height: "auto" }}
          />
        </main>
      );
    }

03 / CONFIGURATION

Allow your image domains.

Choose where originals come from.
Set the list in Cloudflare.

The ALLOWED_HOSTS environment variable lists the hosts imgt can fetch original images from. For https://cdn.example.com/photo.jpg, allow cdn.example.com. Include your frontend domain only if it serves the originals. This setting does not restrict which websites can display optimized images.

Set it in the dashboard.

In Cloudflare, open Workers & Pages, select your Worker, then go to Settings → Variables and Secrets. Add or edit a Text variable named ALLOWED_HOSTS.

Paste your comma-separated hosts and select Deploy. Keep images.unsplash.com for the playground. This value is preserved when you deploy code updates.

After saving, run your production GitHub build before testing images. See the deployment guide for details.

ALLOWED_HOSTS
images.unsplash.com,cdn.example.com,*.images.example.com

How host matching works
Entry Allows
cdn.example.com That exact host. Subdomains need their own entry.
*.images.example.com Any subdomain, including nested ones. The parent images.example.com needs a separate entry.

04 / STORAGE

Set up cache cleanup.

Keep cached images for 30 days.
Regenerate them when needed.

Complete this step before production use. Deployment creates the private imgt R2 bucket, but does not add a lifecycle rule. Without one, unused cached images remain in storage and accumulate.

Add a lifecycle rule.

In Cloudflare, open R2 → imgt → Settings → Object Lifecycle Rules → Add rule. Enter these settings, enable the rule and save it. Use your own bucket name if you changed the default.

R2 lifecycle settings
Setting Value
Rule name expire-cached-images
Prefix derivatives/
Action Delete objects after 30 days
Status Enabled