/ astro-integrations / How to Integrate ImageKit with Astro: Complete Guide
astro-integrations 11 min read

How to Integrate ImageKit with Astro: Complete Guide

Step-by-step guide to integrating ImageKit with your Astro website.

How to Integrate ImageKit with Astro: Complete Guide

ImageKit is a real-time image and video optimization service with a global CDN. You upload your original high-resolution images once, and ImageKit serves optimized versions on the fly. Need a 400px wide WebP thumbnail? Just add URL parameters. Need the same image as a 1200px AVIF? Change the parameters. No pre-processing, no build step, no storing multiple versions.

For Astro sites, this means you can stop worrying about image optimization during the build process. Upload originals to ImageKit, reference them with transformation parameters in your templates, and every visitor gets the right format and size for their device.

Prerequisites

  • Node.js 20 LTS or later. The current official ImageKit Node.js SDK (@imagekit/nodejs) supports only non-EOL Node versions and documents Node.js 20 LTS or later as its requirement, and Astro 6 itself targets modern Node releases.
  • An Astro project (npm create astro@latest). At the time of writing, the latest Astro release is 6.4.2.
  • An ImageKit account (the free tier limits are set on the ImageKit pricing page; confirm the current bandwidth and storage allowances there before relying on a specific number).

Installation

You actually have two separate concerns, and ImageKit ships a separate official package for each.

For server-side work (uploads, file management, signed URLs), install the official Node.js SDK. The current package is @imagekit/nodejs (latest 7.6.2). Note that the older imagekit package (last at 6.0.0, unmaintained for over a year) and a package named imagekitio-core are not the current recommendation. imagekitio-core is not a published package on the npm registry at all, so do not try to install it.

npm install @imagekit/nodejs

For building transformation URLs on the client, install the official JavaScript SDK (current package @imagekit/javascript, latest 5.4.0):

npm install @imagekit/javascript

One important point before you write any code: most of the URL-based work in this guide needs no SDK at all. ImageKit transformations are plain URL path segments (/tr:w-300,h-300/), so an Astro component can build them with simple string concatenation. Reach for the SDKs when you want typed transformation builders (helper.buildSrc / buildSrc) or server-side uploads.

Configuration

Get your credentials from the ImageKit dashboard under Developer Options:

  • URL Endpoint (your unique ImageKit URL)
  • Public Key (for client-side uploads)
  • Private Key (for server-side operations)

Add them to your .env. The naming matters here. Astro exposes only variables prefixed with PUBLIC_ to client-side bundles; everything else stays server-only. The URL endpoint is safe to publish, so it gets the PUBLIC_ prefix. The private key must never reach the browser, so it stays unprefixed (server-only). Keeping the prefix off the private key is the load-bearing security choice in this file.

PUBLIC_IMAGEKIT_URL=https://ik.imagekit.io/your_id
PUBLIC_IMAGEKIT_KEY=public_xxxx
IMAGEKIT_PRIVATE_KEY=private_xxxx

Create the server-side ImageKit client using the current @imagekit/nodejs SDK. The modern constructor takes privateKey (the SDK reads IMAGEKIT_PRIVATE_KEY from the environment automatically, but passing it explicitly keeps the wiring obvious). URL building and uploads moved to namespaced methods in this SDK, so URL generation is client.helper.buildSrc(...) and uploads are client.files.upload(...).

// src/lib/imagekit.ts
import ImageKit from "@imagekit/nodejs";

export const imagekit = new ImageKit({
  privateKey: import.meta.env.IMAGEKIT_PRIVATE_KEY,
});

const urlEndpoint = import.meta.env.PUBLIC_IMAGEKIT_URL;

export function getImageUrl(
  src: string,
  transforms: { width?: number; height?: number; quality?: number; format?: string } = {}
): string {
  // helper.buildSrc accepts a structured transformation array
  return imagekit.helper.buildSrc({
    urlEndpoint,
    src,
    transformation: [
      {
        ...(transforms.width ? { width: transforms.width } : {}),
        ...(transforms.height ? { height: transforms.height } : {}),
        ...(transforms.quality ? { quality: transforms.quality } : {}),
        ...(transforms.format ? { format: transforms.format } : {}),
      },
    ],
  });
}

If you would rather not pull in the SDK on the server just to build URLs, the same result is plain string concatenation, since ImageKit transformations are URL path segments:

export function getImageUrl(
  path: string,
  transforms: { width?: number; height?: number; quality?: number; format?: string } = {}
): string {
  const params: string[] = [];

  if (transforms.width) params.push(`w-${transforms.width}`);
  if (transforms.height) params.push(`h-${transforms.height}`);
  if (transforms.quality) params.push(`q-${transforms.quality}`);
  if (transforms.format) params.push(`f-${transforms.format}`);

  const transformString = params.length > 0 ? `/tr:${params.join(",")}` : "";
  return `${import.meta.env.PUBLIC_IMAGEKIT_URL}${transformString}/${path}`;
}

Basic Usage

Use ImageKit URLs directly in your Astro components with URL-based transformations:

---
// src/components/OptimizedImage.astro
export interface Props {
  src: string;
  alt: string;
  width?: number;
  height?: number;
  quality?: number;
}

const { src, alt, width = 800, height, quality = 80 } = Astro.props;
const baseUrl = import.meta.env.PUBLIC_IMAGEKIT_URL;

// Build transformation string
const transforms = [`w-${width}`, `q-${quality}`];
if (height) transforms.push(`h-${height}`);

const imageUrl = `${baseUrl}/tr:${transforms.join(",")}/${src}`;
const webpUrl = `${baseUrl}/tr:${transforms.join(",")},f-webp/${src}`;
const avifUrl = `${baseUrl}/tr:${transforms.join(",")},f-avif/${src}`;
---

<picture>
  <source srcset={avifUrl} type="image/avif" />
  <source srcset={webpUrl} type="image/webp" />
  <img src={imageUrl} alt={alt} width={width} height={height} loading="lazy" />
</picture>

Use it in your pages:

---
import OptimizedImage from "../components/OptimizedImage.astro";
---

<OptimizedImage src="blog/hero-image.jpg" alt="Article hero" width={1200} height={630} />

Responsive Images

Generate responsive image sets with a single source image:

---
export interface Props {
  src: string;
  alt: string;
  sizes?: string;
}

const { src, alt, sizes = "(max-width: 768px) 100vw, 800px" } = Astro.props;
const baseUrl = import.meta.env.PUBLIC_IMAGEKIT_URL;

const widths = [400, 600, 800, 1200, 1600];
const srcset = widths
  .map((w) => `${baseUrl}/tr:w-${w},f-auto,q-80/${src} ${w}w`)
  .join(", ");
---

<img
  src={`${baseUrl}/tr:w-800,f-auto,q-80/${src}`}
  srcset={srcset}
  sizes={sizes}
  alt={alt}
  loading="lazy"
  decoding="async"
/>

The f-auto parameter tells ImageKit to serve the best format for each browser automatically. Chrome gets AVIF, Safari gets WebP, older browsers get JPEG.

Server-Side Uploads

Uploads run on the server, so this is the one place in the guide where Astro's rendering mode matters. In current Astro the default output is static, meaning every page and endpoint is prerendered at build time. A prerendered endpoint cannot accept a runtime POST. Two things are required to make an upload route work.

First, install an adapter for your host (for example @astrojs/node, @astrojs/netlify, @astrojs/vercel, or @astrojs/cloudflare) and add it in astro.config.mjs. Since Astro 5 the old output: 'hybrid' mode no longer exists. It was removed in favor of static behaving like the former hybrid, so you keep the default static output and opt individual routes into on-demand rendering.

Second, mark the upload endpoint itself as rendered on demand with export const prerender = false. Per the Astro docs, you add that line at the top of the individual page or endpoint you want rendered on demand.

// src/pages/api/upload-image.ts
import type { APIRoute } from "astro";
import { imagekit } from "../../lib/imagekit";

// Required in current Astro: opt this endpoint into on-demand rendering.
// (An adapter must also be configured in astro.config.mjs.)
export const prerender = false;

export const POST: APIRoute = async ({ request }) => {
  try {
    const formData = await request.formData();
    const file = formData.get("file") as File;
    const folder = (formData.get("folder") as string) || "/blog";

    if (!file) {
      return new Response(JSON.stringify({ error: "No file provided" }), {
        status: 400,
      });
    }

    const buffer = Buffer.from(await file.arrayBuffer());
    const base64 = buffer.toString("base64");

    // @imagekit/nodejs namespaces upload under files.upload(...)
    const result = await imagekit.files.upload({
      file: base64,
      fileName: file.name,
      folder: folder,
    });

    return new Response(
      JSON.stringify({
        url: result.url,
        filePath: result.filePath,
        fileId: result.fileId,
      }),
      { status: 200, headers: { "Content-Type": "application/json" } }
    );
  } catch (error) {
    console.error("Upload error:", error);
    return new Response(JSON.stringify({ error: "Upload failed" }), {
      status: 500,
    });
  }
};

Using ImageKit in Blog Posts

Reference images in your MDX frontmatter with ImageKit paths:

---
heroImage: "blog/my-article/hero.jpg"
---

Then in your blog layout, transform on the fly:

---
const { heroImage } = post.data;
const baseUrl = import.meta.env.PUBLIC_IMAGEKIT_URL;
const heroUrl = heroImage
  ? `${baseUrl}/tr:w-1200,h-630,f-auto,q-85/${heroImage}`
  : null;
---

{heroUrl && <img src={heroUrl} alt={post.data.heroImageAlt} width={1200} height={630} />}

Production Tips

  1. Use named transforms. In the ImageKit dashboard, create named transforms for common sizes (thumbnail, hero, og-image). Use them with n-transform_name in URLs for cleaner markup.

  2. Enable automatic format selection. The f-auto parameter serves the best format per browser. This alone can cut image sizes by 30-50% compared to serving JPEG everywhere.

  3. Set up a custom domain. Map images.yourdomain.com to your ImageKit endpoint. This improves perceived trust and SEO since images come from your domain.

  • Purge cache when needed. ImageKit caches transformations on their CDN. If you replace an image with the same filename, purge the cache from the dashboard or API to see changes immediately.

  • Use overlays for social images. ImageKit can add text and image overlays via URL parameters. Generate Open Graph images dynamically without a build step: add your blog title as a text overlay on a template background.

  • Alternatives to Consider

    • Cloudinary if you need more advanced video processing and AI-based transformations (check its current free-tier quota before committing).
    • Cloudflare Images if you are already on Cloudflare and want simpler flat-rate pricing per image served.
    • Astro's built-in image optimization if your site is fully static and you do not need a CDN or dynamic transformations.

    Common Errors and Fixes

    Cannot find module 'imagekitio-core' on install. That package does not exist on the npm registry. Use @imagekit/javascript for client-side URL building and @imagekit/nodejs for server-side work.

    imagekit.upload is not a function after upgrading. The legacy imagekit package exposed a flat upload() method. The current @imagekit/nodejs SDK namespaces methods, so the call is imagekit.files.upload(...) and URL generation is imagekit.helper.buildSrc(...). Update the call sites when you move off the old package.

    Upload route returns a 404 or a prerendered HTML page instead of running. In current Astro the default output is static, so endpoints are prerendered unless you opt out. Add export const prerender = false to the endpoint and configure a server adapter. Without the adapter, on-demand routes have nowhere to run.

    Looking for output: 'hybrid' in config. It was removed in Astro 5. Keep the default static output and opt specific routes into on-demand rendering with export const prerender = false. The Astro team merged output: 'hybrid' into output: 'static', so there is no separate hybrid mode to set.

    The private key leaks into the client bundle. Astro only exposes variables prefixed with PUBLIC_ to client code; everything else is server-only. Never name the private key PUBLIC_IMAGEKIT_PRIVATE_KEY, and never import src/lib/imagekit.ts (which references the private key) into a component that ships to the browser. Build URLs with the public endpoint instead.

    SyntaxError: Unexpected token or runtime engine warnings on older Node. The @imagekit/nodejs SDK supports only non-EOL Node versions and documents Node.js 20 LTS or later. Upgrade Node if you are on 18 or below.

    An unsupported transformation parameter does not error but does not apply. The JavaScript SDK adds parameters it does not explicitly model into the URL as-is. If a transform silently does nothing, check it against the ImageKit image transformation reference for the exact parameter spelling.

    Replaced an image but the old version still serves. ImageKit caches transformed assets on its CDN. When you overwrite a file with the same name, purge the cache (dashboard or API) so the new version propagates.

    Official Docs and Examples

    Wrapping Up

    ImageKit removes image optimization from your build process and moves it to the edge. For Astro sites with lots of visual content, this means faster builds, smaller page sizes, and automatic format negotiation for every visitor. The URL-based transformation API is simple to use in Astro templates, the free tier covers most blogs and portfolios, and the only place you need the server is the upload route. Upload your originals, build your URLs with the right parameters, and let ImageKit handle the rest.

    Sources

    All versions and behavior below were checked on 2026-05-29.