diff --git a/skills/frameworks/cloudinary-next/SKILL.md b/skills/frameworks/cloudinary-next/SKILL.md
index 899e519..55d1d70 100644
--- a/skills/frameworks/cloudinary-next/SKILL.md
+++ b/skills/frameworks/cloudinary-next/SKILL.md
@@ -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
@@ -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
@@ -39,11 +44,11 @@ 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
@@ -51,6 +56,8 @@ This skill is organized for progressive loading. Do not load every reference fil
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.
@@ -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.
diff --git a/skills/frameworks/cloudinary-next/references/api-decision-tree.md b/skills/frameworks/cloudinary-next/references/api-decision-tree.md
index b0ed68e..5b9bae5 100644
--- a/skills/frameworks/cloudinary-next/references/api-decision-tree.md
+++ b/skills/frameworks/cloudinary-next/references/api-decision-tree.md
@@ -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 `` of a Cloudinary asset in JSX | **`CldImage`** (`next-cloudinary`) | Client component (`"use client"`) **or** Server Component |
+| Render an `` 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 ``, ``, 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 |
@@ -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 **``** 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 `` or `` 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 **``**. For "I just need the URL string" the answer is **`getCldImageUrl`**.
diff --git a/skills/frameworks/cloudinary-next/references/cldimage-transformations.md b/skills/frameworks/cloudinary-next/references/cldimage-transformations.md
index c64599b..8113c11 100644
--- a/skills/frameworks/cloudinary-next/references/cldimage-transformations.md
+++ b/skills/frameworks/cloudinary-next/references/cldimage-transformations.md
@@ -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' }` |
@@ -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` |
diff --git a/skills/frameworks/cloudinary-next/references/cldimage.md b/skills/frameworks/cloudinary-next/references/cldimage.md
index 0c95bdb..dbf7335 100644
--- a/skills/frameworks/cloudinary-next/references/cldimage.md
+++ b/skills/frameworks/cloudinary-next/references/cldimage.md
@@ -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 `` 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.
-- ✅ `` 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, `` 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, `` always needs a Client Component.
### Sample assets (use for demos when no upload yet)
diff --git a/skills/frameworks/cloudinary-next/references/imports.md b/skills/frameworks/cloudinary-next/references/imports.md
index 7d4a77d..60fc190 100644
--- a/skills/frameworks/cloudinary-next/references/imports.md
+++ b/skills/frameworks/cloudinary-next/references/imports.md
@@ -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';
@@ -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`.
diff --git a/skills/frameworks/cloudinary-next/references/migrating-to-v7.md b/skills/frameworks/cloudinary-next/references/migrating-to-v7.md
new file mode 100644
index 0000000..ae87029
--- /dev/null
+++ b/skills/frameworks/cloudinary-next/references/migrating-to-v7.md
@@ -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
diff --git a/skills/frameworks/cloudinary-next/references/og-images.md b/skills/frameworks/cloudinary-next/references/og-images.md
index daf572e..11ce363 100644
--- a/skills/frameworks/cloudinary-next/references/og-images.md
+++ b/skills/frameworks/cloudinary-next/references/og-images.md
@@ -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 {
+export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise {
+ 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 `` inside the page (NOT inside ``); it injects the meta tags:
```tsx
@@ -46,4 +50,4 @@ export default function Page() {
```
- ❌ **`` 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.
diff --git a/skills/frameworks/cloudinary-next/references/project-setup.md b/skills/frameworks/cloudinary-next/references/project-setup.md
index e23be47..d16e5ca 100644
--- a/skills/frameworks/cloudinary-next/references/project-setup.md
+++ b/skills/frameworks/cloudinary-next/references/project-setup.md
@@ -9,13 +9,20 @@ If the user is **not** using a CLI scaffold and only has these rules, generate t
**1. Install**
+For a **new** install, pick the `next-cloudinary` major version from the project's Next.js version. If the project already has `next-cloudinary`, keep its major version unless the user wants to upgrade (see `references/migrating-to-v7.md`).
+
+| Next.js | Install | `package.json` range |
+| --- | --- | --- |
+| 16 | `npm install next-cloudinary@7` (v7: also needs React 18.2 or 19 and Node.js 22+) | `"^7.0.0"` |
+| 15 or earlier | `npm install next-cloudinary@6` | `"^6.0.0"` |
+
```bash
-npm install next-cloudinary
# Add the Node SDK only if you do server-side uploads/deletes/signing:
npm install cloudinary
```
-Use `npm install ` with **no version** so npm gets the latest compatible version. In `package.json` use a **caret range** (`"next-cloudinary": "^6.0.0"`). Do not pin exact versions unless verified on npm.
+- ❌ **WRONG**: `npm install next-cloudinary` (no version) on Next.js 15 or earlier. npm picks v7, whose peer dependencies require Next.js 16, and refuses the install.
+- Do not pin exact versions unless verified on npm.
**2. Environment (`.env.local`)**
@@ -64,7 +71,7 @@ Create `app/api/sign-cloudinary-params/route.ts` (App Router) — see **"Signed
**5. Summary for rules-only users**
- **Env**: `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` is required; add `NEXT_PUBLIC_CLOUDINARY_API_KEY` + `CLOUDINARY_API_SECRET` only when signing uploads. Restart dev server after edits.
- **No `Cloudinary` instance to construct**: `next-cloudinary` reads env automatically. Just import components/helpers and use them.
-- **Components are client-side**: `CldUploadWidget`, `CldUploadButton`, `CldVideoPlayer` always need `"use client"`. `CldImage` works in both, but make the file a Client Component if you also use refs / event handlers / state with it.
+- **Client vs server**: any file that passes functions (event handlers, refs, the `CldUploadWidget` render function) needs `"use client"`. In v7, components without those can render from a Server Component. In v6, every component needs `"use client"` (see `references/v6.md`).
- **Server work uses the Node SDK** (`cloudinary` v2) inside Server Actions or route handlers — never in client code.
## Upload Presets
@@ -82,7 +89,7 @@ Create `app/api/sign-cloudinary-params/route.ts` (App Router) — see **"Signed
## Installing Cloudinary packages
-- ✅ **Install latest**: `npm install ` (no version) so npm gets the latest compatible. Use a caret in `package.json`. Do not pin to an exact version unless verified on npm.
+- ✅ **Install the right major**: for `next-cloudinary`, follow the Next.js version table above. For `cloudinary`, `npm install cloudinary` (no version). Use a caret in `package.json`. Do not pin to an exact version unless verified on npm.
- ✅ **Package names only**: `next-cloudinary` (the Next.js wrapper), `cloudinary` (Node SDK v2 — server-side only). Do not invent names like `@cloudinary/next` or `next-cloudinary-server`.
- ❌ **WRONG**: `npm install next-cloudinary@1.2.3` (exact pin) when you haven't verified the version exists.
- ❌ **WRONG**: Installing `@cloudinary/url-gen` or `@cloudinary/react` for a Next.js project — `next-cloudinary` already wraps the URL builder. Only install those if the user explicitly wants to bypass `next-cloudinary`.
diff --git a/skills/frameworks/cloudinary-next/references/quick-checklist.md b/skills/frameworks/cloudinary-next/references/quick-checklist.md
index e6a0358..fbbf4f4 100644
--- a/skills/frameworks/cloudinary-next/references/quick-checklist.md
+++ b/skills/frameworks/cloudinary-next/references/quick-checklist.md
@@ -11,7 +11,7 @@ Use this at the end of generation or review tasks to catch common Cloudinary + N
- ✅ Store `public_id` from upload results (and asset type / dimensions if you'll need them) — not the full URL.
- ✅ For client uploads, default to **unsigned**; switch to signed only when explicitly requested.
- ✅ Never hold the API secret on the client; never prefix it with `NEXT_PUBLIC_`.
-- ✅ Add `"use client"` at the top of any file that uses `CldUploadWidget`, `CldUploadButton`, or `CldVideoPlayer`.
+- ✅ Add `"use client"` at the top of any file that passes functions to a `next-cloudinary` component (event handlers, refs, the `CldUploadWidget` render function). On v6, add it to any file that uses a `next-cloudinary` component.
- ✅ Use the **Node SDK v2** (`import { v2 as cloudinary } from 'cloudinary'`) for all server-side upload/delete/sign work; run on the Node runtime.
- ✅ Pass `invalidate: true` on `destroy` to evict the CDN cache.
- ✅ Use TypeScript: type upload results, env vars, refs.
@@ -22,7 +22,8 @@ Use this at the end of generation or review tasks to catch common Cloudinary + N
When something isn't working, check:
- [ ] **Picked the right tool?** → `` for JSX images, `getCldImageUrl` for URL strings, `` for browser uploads, `cloudinary.uploader.upload` (Node SDK, server) for server-side uploads, `cloudinary.uploader.destroy` (Node SDK, server) for deletes
-- [ ] **`"use client"`** at the top of any file using ``, ``, ``, or any `` with event handlers / refs
+- [ ] **Version**: code matches the installed next-cloudinary major (v7 by default; `references/v6.md` for v6)
+- [ ] **`"use client"`** at the top of any file passing event handlers, refs, or the `` render function (v6: any file using a `next-cloudinary` component)
- [ ] **Env vars**: `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` set; `CLOUDINARY_API_SECRET` (server only, NEVER prefixed with `NEXT_PUBLIC_`); dev server restarted after edits
- [ ] **`.env.local` in `.gitignore`** — never commit secrets
- [ ] **``** is a Cloudinary public ID (no extension, no leading slash) — or a full URL with `/v1234/` and `preserveTransformations`
@@ -31,11 +32,12 @@ When something isn't working, check:
- [ ] **``**: `"use client"` + `import 'next-cloudinary/dist/cld-video-player.css'`
- [ ] **`` unsigned**: preset is **Unsigned** in the dashboard, name matches `uploadPreset` exactly
- [ ] **`` signed**: `signatureEndpoint="/api/sign-cloudinary-params"`; route returns `{ signature }`; uses Node SDK v2 (`import { v2 as cloudinary } from 'cloudinary'`); runs on Node runtime
-- [ ] **Use `onSuccess`** (not deprecated `onUpload`); narrow `result.info` with a type guard before reading `public_id`
+- [ ] **Use `onSuccess`** (`onUpload` was removed in v7 and is deprecated in v6); narrow `result.info` with a type guard before reading `public_id`
- [ ] **Server uploads**: `"use server"` (or route handler), Node runtime, convert `File` → `Buffer` before `upload_stream`
- [ ] **Delete**: `cloudinary.uploader.destroy(publicId, { resource_type, invalidate: true })`; `result === 'ok'` on success
- [ ] **OG images**: App Router → `getCldOgImageUrl` in `generateMetadata`; Pages Router → `` outside ``
- [ ] **Overlays**: `publicId` (image) or `text` (text) per overlay; gravity uses underscores (`"south_east"`); blend modes go in `appliedEffects`
-- [ ] **Installing Cloudinary packages**: `npm install ` with no version; use caret in `package.json`; valid names are `next-cloudinary` and `cloudinary`
+- [ ] **Installing Cloudinary packages**: `next-cloudinary@7` on Next.js 16, `next-cloudinary@6` on Next.js 15 or earlier; use caret in `package.json`; valid names are `next-cloudinary` and `cloudinary`
+- [ ] **OG images (App Router)**: on Next.js 15+, `await params` in `generateMetadata`
- [ ] **TypeScript**: define an upload-result interface; type env vars in `env.d.ts`; use `unknown` + guards instead of `any`
- [ ] **Sample assets** (`samples/cloudinary-icon`, `samples/elephants`, etc.) may have been deleted — handle `onError` and provide an upload path
diff --git a/skills/frameworks/cloudinary-next/references/troubleshooting.md b/skills/frameworks/cloudinary-next/references/troubleshooting.md
index 34abe60..8083690 100644
--- a/skills/frameworks/cloudinary-next/references/troubleshooting.md
+++ b/skills/frameworks/cloudinary-next/references/troubleshooting.md
@@ -4,6 +4,7 @@ Use this when the user provides an error message, broken behavior, or asks for a
## Table of contents
+- Install errors
- Environment variable errors
- Component / `use client` errors
- Import errors
@@ -18,6 +19,12 @@ Use this when the user provides an error message, broken behavior, or asks for a
# ⚠️ COMMON ERRORS & SOLUTIONS
+## Install Errors
+
+### `npm install next-cloudinary` fails with `ERESOLVE` / peer dependency conflict
+- ❌ Problem: the project is on Next.js 15 or earlier. next-cloudinary v7 requires Next.js 16 (and React 18.2 or 19, Node.js 22+).
+- ✅ Solution: install v6 with `npm install next-cloudinary@6`, or upgrade Next.js to 16 first. Don't use `--force` or `--legacy-peer-deps` to push v7 onto an older Next.js.
+
## Environment Variable Errors
### "Cloud name is required" / images don't render
@@ -42,23 +49,27 @@ Use this when the user provides an error message, broken behavior, or asks for a
## Component / "use client" errors
-### "You're importing a component that needs `useState` (or `useEffect`, `createContext`, etc.). It only works in a Client Component."
-- ❌ Problem: ``, ``, or `` rendered in a Server Component without `"use client"`.
-- ✅ Solution: add `"use client"` at the top of the file (or extract the widget/player into its own client component and import it from a server file).
-
-### `CldImage` works in a server file but breaks when I add an `onLoad` handler
-- ❌ Problem: Event handlers can only be passed to Client Components.
-- ✅ Solution: add `"use client"` at the top of the file, or split: keep `` as a child in a server file, but render it from a small `"use client"` wrapper that owns the handler.
+### "Functions cannot be passed directly to Client Components" / "Event handlers cannot be passed to Client Component props"
+- ❌ Problem: a Server Component passes a function to a `next-cloudinary` component: an event handler (`onSuccess`, `onLoad`, `onPlay`, …), a ref, or the render-function child of ``.
+- ✅ Solution: add `"use client"` at the top of the file, or move the component and its handlers into a small Client Component and render that from the server file. To call server code on upload, `` and `` accept Server Actions in their `*Action` props (e.g. `onSuccessAction`).
-### "useRouter is not a function" / random hook errors near ``
-- ❌ Problem: Same — missing `"use client"` directive, or an old version of `next-cloudinary` mismatched with your Next.js version.
-- ✅ Solution: confirm `"use client"`; upgrade to the latest `next-cloudinary` (`npm install next-cloudinary@latest`).
+### "You're importing a component that needs `useState`…" / `useState is not a function` / `useRef is not a function` (v6)
+- ❌ Problem: on next-cloudinary v6, a component (including ``) is rendered from a Server Component. v6 components don't include `'use client'`.
+- ✅ Solution: add `"use client"` to the file, or wrap the component in a Client Component. On Next.js 16, upgrading to v7 also fixes this (see `references/migrating-to-v7.md`).
## Import Errors
### "Cannot find module 'next-cloudinary/dist/cld-video-player.css'"
- ❌ Problem: Wrong path or the package isn't installed.
-- ✅ Solution: install `next-cloudinary` (latest), then import exactly: `import 'next-cloudinary/dist/cld-video-player.css';`. Don't use `cloudinary-video-player/...` — that's the standalone package, not the Next wrapper.
+- ✅ Solution: install `next-cloudinary`, then import exactly: `import 'next-cloudinary/dist/cld-video-player.css';`. Don't use `cloudinary-video-player/...` — that's the standalone package, not the Next wrapper.
+
+### "Can't resolve 'next-cloudinary/cld-video-player.css'"
+- ❌ Problem: the short CSS path only exists in v7. The project is on v6.
+- ✅ Solution: use `next-cloudinary/dist/cld-video-player.css`, which works in both.
+
+### "Module not found: Can't resolve 'next-cloudinary/dist/…'" / `ERR_PACKAGE_PATH_NOT_EXPORTED`
+- ❌ Problem: v7 defines an `exports` map, so imports of internal files such as `next-cloudinary/dist/index.js` no longer resolve.
+- ✅ Solution: import from `next-cloudinary`. The only supported file paths are the video player CSS (`next-cloudinary/dist/cld-video-player.css`, or `next-cloudinary/cld-video-player.css` in v7).
### "Module not found: cloudinary" in a route handler
- ❌ Problem: `cloudinary` (Node SDK) wasn't installed.
@@ -110,9 +121,9 @@ Use this when the user provides an error message, broken behavior, or asks for a
- ❌ Problem: The preset is **Signed** in the dashboard, but you used `uploadPreset` without `signatureEndpoint`.
- ✅ Solution: either flip the preset to Unsigned in the dashboard, or add a `signatureEndpoint` (see signed uploads pattern).
-### `onUpload` is never called
-- ❌ Problem: `onUpload` is deprecated in v6.
-- ✅ Solution: use `onSuccess`. The argument shape is similar: `(result, { widget }) => …`.
+### `onUpload` is never called, or "Property 'onUpload' does not exist"
+- ❌ Problem: `onUpload` was deprecated in v6 and removed in v7.
+- ✅ Solution: use `onSuccess`. The second argument is an object: `(result, { widget }) => …` (with `onUpload` it was `(result, widget)`).
### `result.info` is a string, not an object
- ❌ Problem: You're handling a non-success event (e.g. `queues-end`, `display-changed`) where `info` is a string.
@@ -168,15 +179,26 @@ Use this when the user provides an error message, broken behavior, or asks for a
## CldVideoPlayer Errors
### "Cannot find module 'next-cloudinary/dist/cld-video-player.css'"
-- ✅ Solution: install / upgrade `next-cloudinary`. The path is exactly that — no other variant is exposed.
+- ✅ Solution: install `next-cloudinary`. This path works in v6 and v7; v7 also accepts `next-cloudinary/cld-video-player.css`.
+
+### Poster image is broken when `aspectRatio` is set (v7.0.x)
+- ❌ Problem: known limitation with Video Player v4. The video itself plays normally.
+- ✅ Solution: remove `aspectRatio` if it isn't needed, or also set a crop mode such as `cropMode="fill"`, which crops the video to that ratio. `cropMode` isn't in the TypeScript types yet, so it needs a cast. A fix is planned for a follow-up release.
+
+### Custom poster from the Video Player Studio isn't shown
+- ✅ Checks: v7 shows Studio posters automatically; v6 (Video Player v1.x) doesn't. A `poster` prop set in code takes precedence.
### Player styles are broken / unstyled controls
- ❌ Problem: CSS not imported.
- ✅ Solution: `import 'next-cloudinary/dist/cld-video-player.css';` once on a layout or the page using the player.
### Player throws hydration / removeChild errors
-- ❌ Problem: Component is in a Server Component, or the page suspends and unmounts the player at the wrong time.
-- ✅ Solution: add `"use client"` to the file. Wrap the player in its own component if needed so it's not torn down by parent suspense.
+- ❌ Problem: on v6, the component is in a Server Component; or the page suspends and unmounts the player at the wrong time.
+- ✅ Solution: on v6, add `"use client"` to the file. Wrap the player in its own component if needed so it's not torn down by parent suspense.
+
+### Player is blank after navigating away and back
+- ❌ Problem: on v6, the player doesn't re-initialise after it's unmounted and mounted again (for example after client-side navigation away and back).
+- ✅ Solution: fixed in v7. See `references/migrating-to-v7.md`.
### Video doesn't autoplay
- ❌ Problem: Browsers block autoplay with sound. The `autoplay` prop also takes a string `autoplayMode` (e.g. `"on-scroll"`).
@@ -207,11 +229,13 @@ Use this when the user provides an error message, broken behavior, or asks for a
### "Property 'NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME' does not exist on type 'ProcessEnv'"
- ✅ Solution: declare the env vars in an `env.d.ts` (see "Environment Variable Typing" above).
-### "Type 'null' is not assignable to type 'RefObject<…>'"
+### "Type 'RefObject' is not assignable…" / "Type 'null' is not assignable to type 'RefObject<…>'"
- ✅ Solution: type refs with the actual element / instance type:
```ts
- const videoRef = useRef(null);
- const playerRef = useRef(null);
+ import type { CloudinaryVideoPlayer } from 'next-cloudinary';
+
+ const videoRef = useRef(null);
+ const playerRef = useRef(null);
```
---
diff --git a/skills/frameworks/cloudinary-next/references/typescript.md b/skills/frameworks/cloudinary-next/references/typescript.md
index 69fd450..38737ac 100644
--- a/skills/frameworks/cloudinary-next/references/typescript.md
+++ b/skills/frameworks/cloudinary-next/references/typescript.md
@@ -62,13 +62,19 @@ import { v2 as cloudinary, type UploadApiResponse, type UploadApiErrorResponse }
### Refs
```tsx
-import type { CldVideoPlayerProps } from 'next-cloudinary';
import { useRef } from 'react';
+import type { CloudinaryVideoPlayer } from 'next-cloudinary';
-const playerRef = useRef(null); // Cloudinary player instance — type as unknown until you need methods
-const videoRef = useRef(null);
+const playerRef = useRef(null); // pass as
+const videoRef = useRef(null);
```
+- ❌ **WRONG**: `useRef(null)` for `playerRef` — `CldVideoPlayer` rejects it (`Type 'RefObject' is not assignable…`).
+
+### New type errors after upgrading to v7
+
+v7's types are stricter in a few places (`overlay` removed, `assetType` values, `analytics: false`, `CloudinaryLoaderCldOptions.src`). See `references/migrating-to-v7.md`.
+
### Avoid `any`
- ❌ **WRONG**: `const result: any = …`
- ✅ **CORRECT**: `unknown` + a narrowing type guard, or the explicit type from the SDK.
diff --git a/skills/frameworks/cloudinary-next/references/upload-widget.md b/skills/frameworks/cloudinary-next/references/upload-widget.md
index 7932ca8..3c85947 100644
--- a/skills/frameworks/cloudinary-next/references/upload-widget.md
+++ b/skills/frameworks/cloudinary-next/references/upload-widget.md
@@ -17,7 +17,8 @@ import { CldUploadWidget } from 'next-cloudinary';
;
```
-- ✅ **`"use client"` is required**.
+- ✅ **`"use client"` is required** for `CldUploadWidget`: its render-function child can't be passed from a Server Component. The same applies to `CldUploadButton` when you pass callbacks such as `onSuccess`.
+- ✅ In v7, `` with no callbacks, or with Server Actions in its `*Action` props, can render from a Server Component. In v6 it always needs a Client Component.
- ✅ The widget renders **nothing by default** — pass a function-as-children that returns the trigger UI. The function receives `{ open, close, cloudinary, widget, results, error, isLoading }`.
- ✅ Use `` for a one-line drop-in if you don't need custom UI.
- ✅ **Default to unsigned uploads** unless the user explicitly asks for "secure" or "signed". Signed requires a running backend route and will fail out of the box without it.
@@ -53,7 +54,7 @@ import { CldUploadWidget } from 'next-cloudinary';
>{({ open }) => }
```
-- ✅ **Prefer `onSuccess`** — `onUpload` is deprecated.
+- ✅ **Use `onSuccess`** — `onUpload` was deprecated in v6 and removed in v7.
- ✅ The "Action" variants (`onSuccessAction`, `onUploadAddedAction`, etc.) are the same events but only receive `results` (no widget instance) — use them when wiring directly to a Server Action so the callback is serializable.
- ✅ `result.info` shape includes at least: `public_id`, `secure_url`, `url`, `width`, `height`, `format`, `resource_type`, `bytes`, `created_at`. Type it explicitly (see TypeScript section).
- ❌ **Don't** treat `result.info` as always an object — when the event isn't `success` it can be a string. Narrow before accessing.
diff --git a/skills/frameworks/cloudinary-next/references/v6.md b/skills/frameworks/cloudinary-next/references/v6.md
new file mode 100644
index 0000000..c55e3ec
--- /dev/null
+++ b/skills/frameworks/cloudinary-next/references/v6.md
@@ -0,0 +1,30 @@
+# Projects on next-cloudinary v6
+
+Load this when the project uses `next-cloudinary` 6.x. The rest of this skill is written for v7. Where this file differs, follow this file.
+
+## Requirements
+
+- v6 supports Next.js 12 to 16 and React 17 to 19.
+- v7 needs Next.js 16, React 18.2 or 19, and Node.js 22 or later. A project below Next.js 16 must stay on v6 (or upgrade Next.js first).
+- Install v6 explicitly: `npm install next-cloudinary@6`, with a caret range in `package.json` (`"next-cloudinary": "^6.0.0"`). Without `@6`, npm installs the latest major, which refuses to install on Next.js 15 or earlier.
+
+## Differences from the v7 guidance
+
+| Topic | v6 behaviour |
+| --- | --- |
+| Server Components | No `next-cloudinary` component works in a Server Component, including `CldImage`. Put every component in a file with `'use client'`, or in a small Client Component wrapper. The URL helpers (`getCldImageUrl`, `getCldVideoUrl`, `getCldOgImageUrl`) work on the server. |
+| Video player CSS | Only `next-cloudinary/dist/cld-video-player.css` exists. `next-cloudinary/cld-video-player.css` is v7 only. |
+| Video player version | `CldVideoPlayer` uses Cloudinary Video Player v1.x. Custom posters saved in the Video Player Studio are not applied. |
+| `CldVideoPlayer` with `aspectRatio` | The poster loads normally. The v7.0.x poster limitation doesn't apply. |
+| Upload callbacks | `onUpload` still works but is deprecated. Use `onSuccess`, which has the same shape in v6 and v7: `(result, { widget }) => …`. |
+| `fillBackground` `seed` | Ignored. Don't rely on it in v6. |
+| Imports | Paths inside `next-cloudinary/dist/` resolve, but they aren't supported and break on upgrade. Import only from `next-cloudinary` and the CSS file. |
+
+## Next.js version notes
+
+- On Next.js 14 and earlier, `params` in `generateMetadata` and pages is a plain object, not a Promise. Type it as `{ params }: { params: { slug: string } }` and read `params.slug` directly.
+- On Next.js 15 and 16, use the Promise form shown in `references/og-images.md`.
+
+## Upgrading
+
+If the project is on Next.js 16 (or can move to it), v7 is a reasonable upgrade. See `references/migrating-to-v7.md`. Mention it once, not on every task. If the user can't or doesn't want to upgrade, keep helping on v6: it continues to work.
diff --git a/skills/frameworks/cloudinary-next/references/video-player.md b/skills/frameworks/cloudinary-next/references/video-player.md
index bbfd15c..2fd74b9 100644
--- a/skills/frameworks/cloudinary-next/references/video-player.md
+++ b/skills/frameworks/cloudinary-next/references/video-player.md
@@ -1,11 +1,11 @@
# Video player
-Use this for `CldVideoPlayer` setup, required CSS, and client-only video player code.
+Use this for `CldVideoPlayer` setup, required CSS, events, refs, and known limitations.
## CldVideoPlayer — the player
-Wraps the Cloudinary Video Player. **Required**: `width`, `height`, `src`. **Always** import the CSS once on the page (or in a layout).
+Wraps the Cloudinary Video Player: v4 in next-cloudinary v7, v1.x in v6. **Required**: `width`, `height`, `src`. **Always** import the CSS once on the page (or in a layout).
```tsx
'use client';
@@ -20,10 +20,25 @@ import 'next-cloudinary/dist/cld-video-player.css';
/>;
```
-- ✅ **`"use client"` is required** — the player mounts a video element, attaches refs, and uses effects.
-- ✅ CSS path is exactly `next-cloudinary/dist/cld-video-player.css`. No other variant is exposed.
+- ✅ **`"use client"`**: required when you pass event handlers or refs. In v7, a player without them can also render from a Server Component. In v6, always required.
+- ✅ CSS: `next-cloudinary/dist/cld-video-player.css` works in v6 and v7. v7 also accepts `next-cloudinary/cld-video-player.css`.
- ✅ Use `colors`, `fontFace`, `logo`, `poster`, `textTracks` for customization (see CldVideoPlayer configuration page).
-- ✅ For event hooks: `onMetadataLoad`, `onPlay`, `onPause`, `onEnded`, `onError` — each receives `{ player }`. The player exposes `player.duration()`, `player.currentTime()`, etc.
-- ✅ For programmatic control, pass `playerRef` (Cloudinary player) or `videoRef` (HTML video element) refs.
+- ✅ For event hooks: `onDataLoad`, `onMetadataLoad`, `onPlay`, `onPause`, `onEnded`, `onError` — each receives `{ player, video }`. The player exposes `player.duration()`, `player.currentTime()`, etc.
+- ✅ For programmatic control, pass `playerRef` (Cloudinary player) or `videoRef` (HTML video element) refs, typed like this:
+
+```tsx
+import { useRef } from 'react';
+import type { CloudinaryVideoPlayer } from 'next-cloudinary';
+
+const playerRef = useRef(null);
+const videoRef = useRef(null);
+//
+```
- ❌ **Don't** import the `cloudinary-video-player` package directly in a Next.js app — `next-cloudinary` wraps it.
-- ❌ **Don't** put `` in a Server Component without `"use client"`.
+- ❌ **Don't** pass event handlers or refs to `` from a Server Component. Put that code in a file with `"use client"`.
+
+## v7 notes (Video Player v4)
+
+- **Studio posters**: a custom poster saved for a video in the Video Player Studio is shown automatically. Other saved Studio settings (colours, controls, etc.) are not applied; set them with props instead.
+- **Custom CSS**: v7 moved the player from v1.x to v4. If the project styles the player with its own CSS, check that it still applies.
+- **Known limitation (7.0.x): `aspectRatio`**: in next-cloudinary 7.0.x, setting `aspectRatio` makes the poster image fail to load; the video itself plays normally. A fix is planned for a follow-up release. Don't add `aspectRatio` to a player unless the user asks for it, and if the poster matters, remove it. Fallback if the ratio is required: also set a crop mode, e.g. `cropMode="fill"`. This also crops the video to that ratio, and `cropMode` isn't in the TypeScript types yet, so it needs a cast.