Prismic Integration

Cachely has first-class support for Prismic. It handles both Prismic CDN URL patterns and provides a response transformer that rewrites asset URLs in your Prismic API responses.


Supported URL patterns

Prismic serves images and video from two different CDN domains. Cachely handles both:

Images (with optional Image CDN query params):

https://images.prismic.io/your-repo/path/to/image.png?auto=format,compress&w=1024&fm=webp&q=80

Video:

https://your-repo.cdn.prismic.io/your-repo/path/to/video.mp4

Both are rewritten to your tenant URL (query params on images are preserved):

https://your-tenant.cachely.io/your-repo/path/to/image.png?auto=format,compress&w=1024&fm=webp&q=80
https://your-tenant.cachely.io/your-repo/path/to/video.mp4

Tenant setup

When creating your tenant, select Prismic as the CMS type and provide your repository name:

FieldValue
CMSPrismic
Repository nameyour-repo
Website domainyour-site.com

Two origins are used: https://your-repo.cdn.prismic.io (video and some assets) and https://images.prismic.io/your-repo (images with Image CDN). Cachely routes requests to the correct origin based on the URL pattern.

Note: The website domain is important for Prismic. It's sent as the Referer header when fetching from origin, which prevents AccessDenied errors from Prismic's S3-backed storage.


Cachely SDK

Install the Cachely SDK to route Prismic API and asset requests through your edge domain:

npm install @cachely-io/sdk

Basic usage

import { createCachelyFetch } from '@cachely-io/sdk'
import * as prismic from '@prismicio/client'

const cachelyFetch = createCachelyFetch({
  tenant: 'your-tenant',
  provider: 'prismic',
})

const client = prismic.createClient('your-repo', {
  fetch: cachelyFetch,
})

const data = await client.getAllByType('page')

The SDK rewrites both API URLs (*.cdn.prismic.io) and asset URLs (images.prismic.io, prismic-io.imgix.net) to your project's edge domain. Imgix optimization params (?auto=format&w=800) are preserved end-to-end.

Nuxt 3 / 4 + Prismic

For Nuxt + Prismic projects, run the provider-specific onboarding. All three commands below are equivalent — they reach the same rich onboarder:

npx @cachely-io/sdk@latest setup --provider prismic --tenant your-tenant
npx @cachely-io/sdk@latest setup prismic --tenant your-tenant
npx @cachely-io/sdk@latest init prismic --tenant your-tenant

The primary integration surface is composables/useCachelyPrismicClient.ts, generated by the CLI:

// composables/useCachelyPrismicClient.ts (generated)
import { createCachelyPrismicClient } from "@cachely-io/sdk/prismic"
import { repositoryName } from "~~/slicemachine.config.json"

export function useCachelyPrismicClient() {
  return createCachelyPrismicClient({
    repositoryName,
    tenant: "your-tenant",
  })
}

The slicemachine import uses the root-safe ~~/ alias, which points at the project root on both Nuxt 3 and Nuxt 4. Plain ~/ resolves to srcDir (app/ under Nuxt 4 compatibilityVersion: 4), so it would look for app/slicemachine.config.json and fail.

Use it in your pages:

<script setup lang="ts">
const client = useCachelyPrismicClient()
const { data: page } = await useAsyncData('home', () => client.getSingle('page_home'))
</script>

Pages that still use usePrismic().client continue to hit Prismic directly until you switch them to useCachelyPrismicClient() — unless the canonical client integration (below) covers them.

Greenfield and safely-patchable projects: canonical client included

On greenfield projects (no app/prismic/client.{js,ts} present) and on projects whose existing client is trivially patchable (one simple createClient(repositoryName) call with no options), --yes also writes/patches app/prismic/client.js and points @nuxtjs/prismic at it via config/nuxt/prismic.{js,ts}.

A greenfield file uses the same canonical factory as the composable — createCachelyPrismicClient — so the two surfaces never drift:

// app/prismic/client.js (generated on greenfield)
import { createCachelyPrismicClient } from "@cachely-io/sdk/prismic"
import { repositoryName } from "~~/slicemachine.config.json"

export const cachelyPrismicClient = createCachelyPrismicClient({
  repositoryName,
  tenant: "your-tenant",
})

export default cachelyPrismicClient

createCachelyPrismicClient returns a client that is instanceof @prismicio/client's Client, so @nuxtjs/prismic adopts it directly. Routing needs no environment variable{ tenant: "your-tenant" } resolves to https://your-tenant.cachely.io/~api/... identically in SSR, Nitro, and browser navigation.

When the CLI instead safely patches an existing simple createClient(repositoryName), it keeps your call and just injects the Cachely fetch in place (also env-var-free):

// app/prismic/client.js (existing simple client, safely patched)
import { createClient } from "@prismicio/client"
import { repositoryName } from "~/slicemachine.config.json"
import { createCachelyFetch } from "@cachely-io/sdk"

const cachelyFetch = createCachelyFetch({
  tenant: "your-tenant",
  provider: "prismic",
})

export default createClient(repositoryName, { fetch: cachelyFetch })
// config/nuxt/prismic.js (patched if present)
export default {
  endpoint: config.repositoryName,
  client: "~/app/prismic/client"
}

With both files in place, usePrismic().client is also routed through Cachely.

Complex existing clients: not modified, manual instructions printed

If app/prismic/client.{js,ts} already exists with custom options (access token, routes, link resolver, etc.), the CLI does not modify it. It generates only the composable and prints a loud warning + manual patch instructions. Re-run with --patch-client to force the canonical path — but only after confirming your custom logic survives the template shape.

What is not auto-patched

  • nuxt.config.ts/.js/.mjs is never touched, and the default Prismic setup needs no nuxt.config change. The tenant slug alone drives routing (https://<tenant>.cachely.io/~api/...), so there is no CACHELY_PROXY_URL / runtimeConfig to set — that env var was server-only and silently bypassed Cachely on client-side navigation, which is exactly what this setup avoids.
  • Inline prismic blocks in nuxt.config are not patched — the CLI only touches the standalone config/nuxt/prismic.{js,ts} file when present.

Do not do this

  • Do not replace the Prismic endpoint with https://your-tenant.cachely.io/~api/api/v2. Prismic v7 / @nuxtjs/prismic throw "A repository name is required..." when the repository name cannot be inferred.
  • Do not put clientConfig.fetch in Nuxt runtime config — Nuxt strips functions during runtimeConfig serialization, so the fetch arrives at the runtime plugin as undefined.

cachely.config.json is a tooling manifest written by the CLI (records provider, framework, integration mode, transforms, experiments). Your Nuxt runtime does not import it.

After onboarding

rm -rf .nuxt
npm run dev

If the canonical client was written, the startup log includes:

Using user-defined `client` at `~/app/prismic/client.js`

Network requests should go to https://<tenant>.cachely.io/~api/api/v2/..., and asset URLs inside the API JSON should be rewritten to https://<tenant>.cachely.io/<repositoryName>/....

CLI flags

FlagDefaultWhat it does
--composables(inferred — see --yes rules)Generate composables/useCachelyPrismicClient.ts only.
--patch-client(inferred)Write/patch the canonical app/prismic/client.js + config/nuxt/prismic.
--composables --patch-client(inferred)Both.
--no-patch-clientEquivalent to --composables alone.
--transformsoffWrite docs/cachely/transforms.md documenting the per-call cachely: { transform } API.
--experimentsoffWire useCachelyExperiments() into the composable; print the Nuxt-module registration snippet.
--custom-domain / --no-custom-domainoffGenerate customDomain: true in the client (use when the Cachely project has a custom domain with website-proxy enabled).

Rewriting URLs without intercepting fetch

If you can't replace the fetch implementation, use createCachelyUrlRewriter:

import { createCachelyUrlRewriter } from '@cachely-io/sdk'

const rewrite = createCachelyUrlRewriter({
  tenant: 'your-tenant',
  provider: 'prismic',
})

const { url } = rewrite('https://images.prismic.io/your-repo/photo.jpg?w=800&fm=webp')
// → https://your-tenant.cachely.io/your-repo/photo.jpg?w=800&fm=webp

Preview & fresh-fetch bypass

By default, API proxy responses are cached at the edge (typically 60 seconds). For preview mode or fresh-fetch use cases — where you need guaranteed-fresh data from Prismic — you can bypass the cache per request.

The edge proxy already supports this: any API request with ?preview=1 skips cache entirely and fetches directly from upstream. The withCacheBypass wrapper makes it easy to apply this from your app code.

Setup

Create a small wrapper around createCachelyFetch:

// utils/withCacheBypass.ts

export function withCacheBypass(
  baseFetch: typeof fetch,
  bypass: boolean | (() => boolean),
  param = 'preview',
): typeof fetch {
  return function bypassFetch(input, init) {
    const active = typeof bypass === 'function' ? bypass() : bypass
    if (!active) return baseFetch(input, init)

    const url = typeof input === 'string'
      ? input
      : input instanceof URL ? input.href : input.url
    if (!url) return baseFetch(input, init)

    const parsed = new URL(url)
    if (!parsed.searchParams.has(param)) {
      parsed.searchParams.set(param, '1')
    }

    if (input instanceof Request) {
      return baseFetch(new Request(parsed.toString(), input), init)
    }
    return baseFetch(parsed.toString(), init)
  }
}

Nuxt integration example

// plugins/prismic.ts
import * as prismic from '@prismicio/client'
import { createCachelyFetch } from '@cachely-io/sdk'
import { withCacheBypass } from '~/utils/withCacheBypass'

export default defineNuxtPlugin(() => {
  const { enabled: isPreview } = usePreviewMode()

  const baseFetch = createCachelyFetch({
    tenant: 'my-site',
    provider: 'prismic',
  })

  // When preview mode is active, all API requests bypass edge cache
  const cachelyFetch = withCacheBypass(baseFetch, () => isPreview.value)

  const client = prismic.createClient('my-repo', {
    fetch: cachelyFetch,
  })

  return { provide: { prismic: { client } } }
})

How it works

  • Normal requests go through the proxy and are served from edge cache when available (X-Cache: HIT or MISS)
  • Preview/fresh requests have ?preview=1 appended automatically, which tells the proxy to skip cache and fetch directly from Prismic (X-Cache: BYPASS)
  • The bypass signal is evaluated per request, so switching preview mode on/off takes effect immediately

Static bypass

If you need a client that always bypasses cache (e.g., for a dedicated preview endpoint), pass true instead of a function:

const previewFetch = withCacheBypass(baseFetch, true)
const previewClient = prismic.createClient('my-repo', { fetch: previewFetch })

What this does not do

  • Does not bypass asset proxy caching. The ?preview=1 param only affects /~api routes. Asset URLs are unaffected — this is intentional, since preview use cases are about fresh API data, not fresh images.
  • Does not change how Prismic preview refs work. Prismic's own preview mechanism uses a ref parameter to point to a draft release. That ref flows through the proxy as a normal query parameter. withCacheBypass ensures the proxy doesn't serve a stale cached version of the previous ref.

Troubleshooting

I ran setup but requests still hit Prismic

The most common cause is that pages still call usePrismic().client or a custom /api/cms/... endpoint instead of the generated composable.

Fix:

  1. In each affected page, swap:
    const { client } = usePrismic()
    

    for:
    const client = useCachelyPrismicClient()
    
  2. If you opted into the canonical client path (--patch-client / --yes on a greenfield or trivially-patchable project), verify config/nuxt/prismic.{js,ts} contains client: "~/app/prismic/client" and the Nuxt startup log says Using user-defined client at ~/app/prismic/client.js.
  3. For custom /api/cms/* server routes, check that the route's implementation imports your generated client (or createCachelyFetch) rather than calling @prismicio/client directly.

useCachelyExperiments is not defined

The composable is registered by the Cachely Nuxt module. If the module isn't registered, useCachelyExperiments() is unresolved at build time.

Fix — add the module + tenant to nuxt.config:

export default defineNuxtConfig({
  modules: ['@cachely-io/sdk/nuxt'],
  cachely: { tenant: 'your-tenant' },
})

cachely.tenant is required for the module to construct the tracker.

Setup did not modify my existing client

By design. When app/prismic/client.{js,ts} already exists with custom options (access token, routes, custom fetch, link resolver, etc.), the CLI does not touch it and prints manual patch instructions instead.

Two options:

  1. Use the generated composableuseCachelyPrismicClient() works alongside any existing client. Switch your pages to it and your existing client becomes unused for those calls.
  2. Manually patch your existing client — apply the printed instructions. The minimal change is to pass fetch: createCachelyFetch({ tenant, provider: 'prismic' }) to createClient(repositoryName, { ... }).

If you're sure the file is safe to overwrite, delete it and re-run setup --provider prismic --tenant <slug> --patch-client.

Still seeing cdn.prismic.io

Applies when you opted into the canonical client path. Delete Nuxt's generated cache and restart:

rm -rf .nuxt
npm run dev

Then confirm config/nuxt/prismic.js includes client: "~/app/prismic/client" and the startup log says Nuxt is using the user-defined client.

"A repository name is required..."

This usually means the Prismic endpoint was replaced with https://<tenant>.cachely.io/~api/api/v2. Restore the normal repository endpoint, and use app/prismic/client.js with createClient(repositoryName, { fetch: createCachelyFetch(...) }). The current CLI never produces this configuration — it can only happen from a hand-written setup.

Asset URLs still use images.prismic.io

First verify API requests go through Cachely at https://<tenant>.cachely.io/~api/api/v2/.... If they do, check that API response URL rewriting is enabled for the project and inspect the rewrite settings/logs.

AI Transforms are not applying

The request is routed through Cachely (you see https://<tenant>.cachely.io/~api/... in DevTools), but the response body comes back untransformed.

Cause: no transform profile was passed for this Prismic call. The SDK only appends ?transform=<profile> when you opt in — either as the adapter default or per call.

Fix — pass the profile per call via the cachely namespace:

const client = useCachelyPrismicClient()

const home = await client.getSingle('page_home', {
  cachely: { transform: 'serbian' },
})

The profile must match an active AI Transform rule in your Cachely dashboard (drafts only run in preview). To make a profile the default for every call from this client, pass transform when constructing the client instead:

createCachelyPrismicClient({
  repositoryName,
  tenant: 'your-tenant',
  transform: 'serbian',
})

Per-call cachely: { transform: null } (or false / undefined / '') disables the default for that one request. The cachely key is stripped before params reach @prismicio/client — it never appears in the outbound URL.


Advanced options

Custom transformers

You can add additional transformers that run after the default Prismic ones:

const transformed = transformPrismicAssetUrls(data, {
  repository: "your-repo",
  transformers: [
    (jsonStr, { base }) => {
      // Custom string replacement on the serialized JSON
      return jsonStr.replaceAll("old-pattern", "new-pattern")
    }
  ]
})

Post-transform hook

Run a function on the parsed result after all URL replacements:

const transformed = transformPrismicAssetUrls(data, {
  repository: "your-repo",
  postTransform: (data) => {
    // Modify the final parsed object
    return data
  }
})

Error handling

By default, transform errors are logged as warnings and the original data is returned unchanged. You can provide a custom error handler:

const transformed = transformPrismicAssetUrls(data, {
  repository: "your-repo",
  onError: (error) => {
    Sentry.captureException(error)
  }
})
Need help understanding this?Ask Cachely Copilot about features, setup, or integrations.
Ask Copilot →