Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions skills/frameworks/cloudinary-next/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
name: cloudinary-next
description: Guidance for building, debugging, and reviewing next.js apps that use cloudinary through next-cloudinary and the cloudinary node sdk v2. Use for tasks involving CldImage, CldVideoPlayer, CldUploadWidget, CldUploadButton, CldOgImage, GetCldImageUrl, GetCldOgImageUrl, GetCldVideoUrl, signed uploads, server uploads, deletes, transformations, responsive images, overlays, social cards, environment variables, and common Cloudinary errors.
description: Guidance for building, debugging, and reviewing next.js apps that use cloudinary through next-cloudinary and the cloudinary node sdk v2. Use for tasks involving CldImage, CldVideoPlayer, CldUploadWidget, CldUploadButton, CldOgImage, GetCldImageUrl, GetCldOgImageUrl, GetCldVideoUrl, signed uploads, server uploads, deletes, transformations, responsive images, overlays, social cards, environment variables, upgrading next-cloudinary from v6 to v7, and common Cloudinary errors.
license: MIT
metadata:
author: cloudinary
version: '1.0.0'
version: '1.1.0'
---

# Cloudinary Next
Expand All @@ -17,6 +17,11 @@ This skill is organized for progressive loading. Do not load every reference fil

## Core workflow

0. Check versions before writing code (once per session):
- Read the installed `next-cloudinary` version from `node_modules/next-cloudinary/package.json`. If it isn't installed yet, read the range in the project's `package.json`. Read the Next.js version the same way from `next`.
- **next-cloudinary 7.x**: follow this skill as written.
- **next-cloudinary 6.x**: also load `references/v6.md`. Where it differs from the rest of this skill, `references/v6.md` wins. If the project could upgrade, mention it once using `references/migrating-to-v7.md`, then carry on with the user's task on v6.
- **Not installed yet**: install the major version that matches the project's Next.js version (see `references/project-setup.md`).
1. Classify the user's goal before writing code:
- render a transformed Cloudinary image in JSX
- generate a Cloudinary URL string
Expand All @@ -39,18 +44,20 @@ This skill is organized for progressive loading. Do not load every reference fil
- Use `next-cloudinary` for Next.js components and URL helpers: `CldImage`, `CldVideoPlayer`, `CldUploadWidget`, `CldUploadButton`, `CldOgImage`, `getCldImageUrl`, `getCldOgImageUrl`, and `getCldVideoUrl`.
- Use the Cloudinary Node SDK v2 only for server-side operations: `import { v2 as cloudinary } from 'cloudinary'`.
- Never expose `CLOUDINARY_API_SECRET` to the browser. Never create `NEXT_PUBLIC_CLOUDINARY_API_SECRET`.
- Put upload widgets, video player UI, and any component with React event handlers behind a Client Component boundary with `'use client'`.
- `CldImage` may be used from a Server Component for static rendering, but if you add client-only props such as `onLoad` or local state, move it into a Client Component.
- Functions can't be passed from a Server Component to a client component. Any file that passes event handlers (`onSuccess`, `onLoad`, `onPlay`, …), refs, or the render-function child of `CldUploadWidget` needs `'use client'`.
- In v7, `CldImage`, `CldVideoPlayer`, `CldUploadButton` and `CldUploadWidget` include `'use client'` themselves, so they can be rendered from a Server Component as long as no functions are passed (Server Actions passed to the `*Action` props are fine). In v6, every `next-cloudinary` component, including `CldImage`, must be rendered from a Client Component.
- Do not import `cloudinary` in Client Components or Edge runtime code. Server Actions and route handlers that import `cloudinary` must run on the Node.js runtime.
- Use documented `next-cloudinary` prop names and shapes. Do not infer prop names from Cloudinary URL transformation parameters.
- Use `onSuccess` for upload widget success handling. Do not use deprecated upload callback names unless the installed version explicitly documents them.
- Use `onSuccess` for upload widget success handling. `onUpload` was deprecated in v6 and removed in v7.
- For deletes, pass a public ID, not a delivery URL. Pass `resource_type` when deleting videos or raw assets, and use `invalidate: true` when CDN cache invalidation is desired.

## Reference map

Load the smallest useful set of references:

- `references/official-docs.md` — official documentation links and the global prop-name rule.
- `references/v6.md` — how projects on next-cloudinary v6 differ from this skill's v7 guidance.
- `references/migrating-to-v7.md` — upgrading a project from v6 to v7: prerequisites, breaking changes, what to change, and when to stay on v6.
- `references/api-decision-tree.md` — choose the correct Cloudinary API/component/helper for a user goal.
- `references/project-setup.md` — install packages, configure Next.js image domains, configure upload presets, and create starter setup files.
- `references/environment.md` — environment variables and TypeScript process env typing.
Expand All @@ -70,7 +77,7 @@ Load the smallest useful set of references:

## Output expectations

When generating code, include the file path, the complete relevant code block, and a short note about where the code runs: Client Component, Server Component, Server Action, or route handler.
When generating code, include the file path, the complete relevant code block, and a short note about where the code runs: Client Component, Server Component, Server Action, or route handler. If the code would differ between next-cloudinary v6 and v7, say which version it targets.

When reviewing code, report issues in this order: secret exposure, server/client boundary mistakes, import/runtime mistakes, incorrect component/helper choice, incorrect prop/event names, missing TypeScript narrowing, and missing cache invalidation or resource type handling.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ This is the single biggest source of confusion in Next.js + Cloudinary. **Pick t

| You want to… | Use this | Where it runs |
| --- | --- | --- |
| Render an `<img>` of a Cloudinary asset in JSX | **`CldImage`** (`next-cloudinary`) | Client component (`"use client"`) **or** Server Component |
| Render an `<img>` of a Cloudinary asset in JSX | **`CldImage`** (`next-cloudinary`) | v7: Server or Client Component (Client if you pass handlers like `onLoad`). v6: Client Component only |
| Get a Cloudinary image URL as a string (for `<meta>`, `<link>`, an API response, an OG image, etc.) | **`getCldImageUrl`** / **`getCldOgImageUrl`** (`next-cloudinary`) | Anywhere — server, client, route handlers, `generateMetadata` |
| Embed a video player UI | **`CldVideoPlayer`** (`next-cloudinary`) | Client component (`"use client"`) — required |
| Let the user upload from the browser | **`CldUploadWidget`** or **`CldUploadButton`** (`next-cloudinary`) | Client component (`"use client"`) — required |
| Embed a video player UI | **`CldVideoPlayer`** (`next-cloudinary`) | v7: Server Component OK without event handlers or refs; Client Component otherwise. v6: Client Component only |
| Let the user upload from the browser | **`CldUploadWidget`** or **`CldUploadButton`** (`next-cloudinary`) | Client Component for `CldUploadWidget` (its render-function child) and for any `onSuccess`-style callback. v7: `CldUploadButton` with no callbacks, or with Server Actions in `*Action` props, can render from a Server Component |
| Generate an OG/social image | App Router → **`getCldOgImageUrl`** in `generateMetadata`. Pages Router → **`CldOgImage`** in the page | Server (App) / Pages Head |
| Upload a file **from your server** (e.g. a Server Action receiving a `FormData`) | **`cloudinary.uploader.upload`** / **`upload_stream`** (Node SDK v2, `cloudinary`) | Server only (`"use server"` action or route handler) |
| Delete an asset | **`cloudinary.uploader.destroy`** (Node SDK v2) | Server only |
Expand All @@ -22,5 +22,5 @@ This is the single biggest source of confusion in Next.js + Cloudinary. **Pick t
**Common confusion to flush out before writing code:**
- ❌ "Should I use `cloudinary.uploader.upload` or `cloudinary.image` or `cloudinary.url`?" → They do **different** things. `cloudinary.uploader.upload` (Node SDK, server) **uploads** a file. `cloudinary.url()` / `cloudinary.image()` (Node SDK, server) build a **URL/HTML tag** from a public ID — for that job, prefer **`getCldImageUrl`** or **`<CldImage>`** in Next.js. They are not interchangeable.
- ❌ Do **not** call `cloudinary.uploader.upload` from a Client Component or browser code — it requires `api_secret`, which must never reach the browser.
- ❌ Do **not** use `<CldUploadWidget>` or `<CldVideoPlayer>` in a Server Component that has not been wrapped in `"use client"` — they use React state/effects and will throw. `CldImage` can be used from a Server Component for static rendering, but client event handlers require a Client Component.
- ❌ Do **not** pass functions (event handlers, refs, the `CldUploadWidget` render function) from a Server Component — Next.js throws `Functions cannot be passed directly to Client Components` or `Event handlers cannot be passed to Client Component props`. Put that code in a file with `"use client"`. On v6, every `next-cloudinary` component must be in a Client Component.
- ✅ For "show an image with transformations" the answer is almost always **`<CldImage>`**. For "I just need the URL string" the answer is **`getCldImageUrl`**.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Use **only** the prop names and shapes below (and on https://next.cloudinary.dev
| --- | --- | --- |
| `removeBackground` | `boolean \| string` | `removeBackground` |
| `replaceBackground` | `boolean \| string \| object` | `"fish tank"` or `{ prompt: 'fish tank', seed: 3 }` |
| `fillBackground` | `boolean \| object` | `fillBackground` or `{ crop: 'pad', gravity: 'south', prompt: 'cupcakes' }` |
| `fillBackground` | `boolean \| object` | `fillBackground` or `{ crop: 'pad', gravity: 'south', prompt: 'cupcakes' }`. v7: add `seed: 3` to regenerate a different result, or to return a previously generated one (ignored in v6) |
| `extract` | `string \| string[] \| object` | `"space jellyfish"` |
| `remove` | `string \| string[] \| object` | `"apple"` or `{ prompt: 'apple', multiple: true, removeShadow: true }` |
| `replace` | `string[] \| object` | `['apple', 'banana']` or `{ from: 'apple', to: 'banana' }` |
Expand All @@ -36,7 +36,7 @@ Use **only** the prop names and shapes below (and on https://next.cloudinary.dev
| `dpr` | `number \| string` | — | `"2.0"` or `"auto"` |
| `unoptimized` | `boolean` | — | Skips both `f_auto` and `q_auto` |
| `deliveryType` | `string` | `upload` | e.g. `"fetch"` |
| `assetType` | `string` | `image` | Use `"video"` to make a thumbnail from a video public ID |
| `assetType` | `string` | `image` | Use `"video"` to make a thumbnail from a video public ID. v7 accepts only `'image'`, `'images'`, `'video'`, `'videos'` or `'all'` |
| `flags` | `string[]` | — | e.g. `['keep_iptc']` |
| `version` | `number \| string` | — | e.g. `1234` |

Expand Down
2 changes: 1 addition & 1 deletion skills/frameworks/cloudinary-next/references/cldimage.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { CldImage } from 'next-cloudinary';
- ✅ A full Cloudinary URL is also accepted as `src` **only if it includes a version segment** (e.g. `/v1234/`). To preserve transformations already in that URL, also pass `preserveTransformations`.
- ✅ Pass a **`sizes`** prop on every `<CldImage>` that isn't a fixed-size icon — this is what powers responsive `srcset`. See "Responsive images".
- ✅ **Optimization is automatic**: by default `format=auto` and `quality=auto` are applied. Don't add raw `f_auto` / `q_auto` yourself.
- ✅ `<CldImage>` can render in a Server Component, **but** if the file uses event handlers (`onLoad`, `onError`), refs, or other client-only APIs, add `"use client"` at the top of the file.
- ✅ In v7, `<CldImage>` can render in a Server Component, **but** if the file uses event handlers (`onLoad`, `onError`), refs, or other client-only APIs, add `"use client"` at the top of the file. In v6, `<CldImage>` always needs a Client Component.

### Sample assets (use for demos when no upload yet)

Expand Down
4 changes: 3 additions & 1 deletion skills/frameworks/cloudinary-next/references/imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { CldImage, CldVideoPlayer, CldUploadWidget, CldUploadButton, CldOgImage
// URL helpers (isomorphic — server, client, metadata, route handlers)
import { getCldImageUrl, getCldOgImageUrl, getCldVideoUrl } from 'next-cloudinary';

// Video player CSS — required only when using CldVideoPlayer
// Video player CSS — required only when using CldVideoPlayer (works in v6 and v7)
import 'next-cloudinary/dist/cld-video-player.css';
// v7 also accepts the shorter path: 'next-cloudinary/cld-video-player.css'

// Server-side Node SDK v2 (only in route handlers / Server Actions / server-only files)
import { v2 as cloudinary } from 'cloudinary';
Expand All @@ -22,3 +23,4 @@ import { v2 as cloudinary } from 'cloudinary';
- ❌ **WRONG**: `import { CldImage } from '@cloudinary/next'` — package does not exist.
- ❌ **WRONG**: `import cloudinary from 'cloudinary'` — gives you v1 namespace; always use `import { v2 as cloudinary } from 'cloudinary'`.
- ❌ **WRONG**: `import 'cloudinary-video-player/cld-video-player.min.css'` — that's the React rules' path. In Next.js use `next-cloudinary/dist/cld-video-player.css`.
- ❌ **WRONG**: importing other files from `next-cloudinary/dist/…` (e.g. `next-cloudinary/dist/index.js`). v7 defines an `exports` map, so these fail with `Module not found: Can't resolve …` (or `ERR_PACKAGE_PATH_NOT_EXPORTED` in Node). Import from `next-cloudinary`.
48 changes: 48 additions & 0 deletions skills/frameworks/cloudinary-next/references/migrating-to-v7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Migrating from next-cloudinary v6 to v7

Use this when a project is on v6 and the user asks about upgrading, or when you recommend the upgrade once at the start of a session.

## Can this project upgrade?

Check before recommending anything:

| Project | Recommendation |
| --- | --- |
| Next.js 16, React 18.2 or 19, Node.js 22+ | Can upgrade now. |
| Next.js 15 or earlier | Needs a Next.js 16 upgrade first. That is a separate, larger change: mention it, but don't push it during an unrelated task. |
| React below 18.2, or Node.js below 22 | Needs those upgraded first. |
| The user doesn't want to upgrade | Stay on v6 (see `references/v6.md`). It continues to work. |

## What the upgrade gives them

- `CldVideoPlayer` uses Cloudinary Video Player v4 (v6 uses v1.x). Custom posters saved in the Video Player Studio are shown automatically.
- Components include `'use client'`, so they can be rendered from Server Components when no functions are passed.
- Fixes: the video player re-initialises after unmount/remount (for example navigating away and back), `q_auto` no longer repeats in video URLs with a `transformation` array, and overlay text with several commas no longer breaks image URLs.
- More complete TypeScript types, and `seed` for `fillBackground`.

## Find what needs changing

Search the project for:

| Search for | Change |
| --- | --- |
| `onUpload` | Removed in v7. Use `onSuccess`. The second argument is an object: `onUpload={(result, widget) => …}` becomes `onSuccess={(result, { widget }) => …}`. |
| Imports from `next-cloudinary/dist/…` other than `dist/cld-video-player.css` | v7 defines an `exports` map. Import from `next-cloudinary` instead. |
| The `overlay` option (singular) | Removed from the types. It was never applied in v6, so replace it with `overlays` (an array) if the overlay is wanted, otherwise delete it. |
| `assetType` values other than `'image'`, `'images'`, `'video'`, `'videos'` or `'all'` | Now a type error. Other values such as `'raw'` already threw at runtime. |
| `analytics: false` passed to `getCldImageUrl` or `getCldVideoUrl` | No longer accepted. It never disabled analytics in v6: remove it. |
| Code that calls `cloudinaryLoader` directly or uses `CloudinaryLoaderCldOptions` | `src` is now required. |
| Option types imported from `@cloudinary-util/url-loader` | Some names and shapes changed. Prefer the types exported by `next-cloudinary`. |
| Custom CSS targeting the video player | The player moved from v1.x to v4 (three majors). Check the styles still apply. |
| `CldVideoPlayer` with `aspectRatio` | Known limitation in 7.0.x: the poster image fails to load (the video plays normally). See `references/video-player.md`. |

`'use client'` wrappers that only existed so a component could render in a Server Component can stay; removing them is optional.

## Steps

1. Confirm Next.js 16, React 18.2 or 19, and Node.js 22 or later.
2. `npm install next-cloudinary@7`
3. Apply the changes found above.
4. Run the type check and a production build (`next build`), then check pages that use `CldVideoPlayer` and `CldUploadWidget` in the browser.

Full release notes: https://github.com/cloudinary-community/next-cloudinary/releases
12 changes: 8 additions & 4 deletions skills/frameworks/cloudinary-next/references/og-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,28 @@ Use this for Open Graph/social card images with `getCldOgImageUrl` in App Router
import type { Metadata } from 'next';
import { getCldOgImageUrl } from 'next-cloudinary';

export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params;
const ogUrl = getCldOgImageUrl({
src: 'blog/cover',
overlays: [
{
text: { color: 'white', fontFamily: 'Source Sans Pro', fontSize: 80, fontWeight: 'bold', text: params.slug },
text: { color: 'white', fontFamily: 'Source Sans Pro', fontSize: 80, fontWeight: 'bold', text: slug },
position: { x: 0, y: 0, gravity: 'center' },
},
],
});

return {
openGraph: { images: [{ url: ogUrl, width: 1200, height: 630 }] },
openGraph: { images: [{ url: ogUrl, width: 1200, height: 627 }] },
twitter: { card: 'summary_large_image', images: [ogUrl] },
};
}
```

- ✅ On Next.js 15 and 16, `params` is a Promise: `await` it as above. Reading `params.slug` directly on Next.js 16 gives `undefined`, and the OG image is generated with empty text, with no build error.
- On Next.js 14 and earlier (next-cloudinary v6 only), `params` is a plain object: see `references/v6.md`.

**Pages Router** — use `<CldOgImage>` inside the page (NOT inside `<Head>`); it injects the meta tags:

```tsx
Expand All @@ -46,4 +50,4 @@ export default function Page() {
```

- ❌ **`<CldOgImage>` does NOT work in the App Router.** Use `getCldOgImageUrl` in `generateMetadata` instead. `next-cloudinary` will warn / silently no-op otherwise.
- ✅ OG images are **1200×630** by default. Override via `width`/`height` if needed.
- ✅ OG images are **1200×627** by default. Override via `width`/`height` if needed.
Loading
Loading