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:
| Field | Value |
|---|---|
| CMS | Prismic |
| Repository name | your-repo |
| Website domain | your-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
Refererheader when fetching from origin, which preventsAccessDeniederrors 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 tosrcDir(app/under Nuxt 4compatibilityVersion: 4), so it would look forapp/slicemachine.config.jsonand 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/.mjsis never touched, and the default Prismic setup needs nonuxt.configchange. The tenant slug alone drives routing (https://<tenant>.cachely.io/~api/...), so there is noCACHELY_PROXY_URL/runtimeConfigto set — that env var was server-only and silently bypassed Cachely on client-side navigation, which is exactly what this setup avoids.- Inline
prismicblocks innuxt.configare not patched — the CLI only touches the standaloneconfig/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/prismicthrow "A repository name is required..." when the repository name cannot be inferred. - Do not put
clientConfig.fetchin Nuxt runtime config — Nuxt strips functions during runtimeConfig serialization, so the fetch arrives at the runtime plugin asundefined.
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
| Flag | Default | What 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-client | — | Equivalent to --composables alone. |
--transforms | off | Write docs/cachely/transforms.md documenting the per-call cachely: { transform } API. |
--experiments | off | Wire useCachelyExperiments() into the composable; print the Nuxt-module registration snippet. |
--custom-domain / --no-custom-domain | off | Generate 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: HITorMISS) - Preview/fresh requests have
?preview=1appended 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=1param only affects/~apiroutes. 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
refparameter to point to a draft release. That ref flows through the proxy as a normal query parameter.withCacheBypassensures 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:
- In each affected page, swap:
const { client } = usePrismic()
for:const client = useCachelyPrismicClient() - If you opted into the canonical client path (
--patch-client/--yeson a greenfield or trivially-patchable project), verifyconfig/nuxt/prismic.{js,ts}containsclient: "~/app/prismic/client"and the Nuxt startup log saysUsing user-defined client at ~/app/prismic/client.js. - For custom
/api/cms/*server routes, check that the route's implementation imports your generated client (orcreateCachelyFetch) rather than calling@prismicio/clientdirectly.
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:
- Use the generated composable —
useCachelyPrismicClient()works alongside any existing client. Switch your pages to it and your existing client becomes unused for those calls. - Manually patch your existing client — apply the printed instructions. The minimal change is to pass
fetch: createCachelyFetch({ tenant, provider: 'prismic' })tocreateClient(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)
}
})