@studio-fes/layer-craft
@studio-fes/layer-craft
Nuxt layer for Craft CMS integration with GraphQL and advanced caching capabilities. Extends @studio-fes/layer-base.
Features
๐ GraphQL Integration
- nuxt-graphql-middleware: Full-featured GraphQL client
- Automatic Query Imports: Auto-import
.gqlfiles fromqueries/directories - Type-Safe: Full TypeScript support with generated types
- Schema Download: Automatic GraphQL schema download in development
โก Caching
- nuxt-multi-cache: Multi-level caching system
- Smart Cache Tags: Automatic cache tag generation
- Server-side Caching: Optimize performance with intelligent caching
๐จ Craft-Specific Features
- CraftImage Component: Optimized image handling for Craft assets
- SEO Composables: Easy SEO management with
useCraftSEO - Org Schema Composables: schema.org / Nuxt SEO structured data from the Craft Toolkit plugin's "Org Schema" panel and field, via
useCraftOrgSchemaIdentityanduseCraftOrgSchema - Entry Composables: Simplified entry fetching with
useCraftEntry - Preview Mode: Built-in Craft preview token support
Installation
npm install @studio-fes/layer-craft
This will automatically include @studio-fes/layer-base and its features.
Usage
Extend this layer in your nuxt.config.ts:
export default defineNuxtConfig({
extends: ['@studio-fes/layer-craft'],
graphqlMiddleware: {
downloadSchema: 'dev-only',
}
})
Environment Variables
# obbligatoria, letta al build: da qui il layer ricava l'endpoint GraphQL (`<url>/graphql`)
# e il dominio ammesso per le immagini con ipx (`image.domains`)
NUXT_PUBLIC_BACKEND_URL=https://your-craft-site.com
NUXT_CACHE_ENABLED=true
# opzionale: cache dati su Redis (runtime)
NUXT_REDIS_URL=redis://:password@host:6379/0
# opzionale: header CDN per Cloudflare
NUXT_CDN_CACHE_ENABLED=true
NUXT_GRAPHQL_MIDDLEWARE_GRAPHQL_ENDPOINT e NUXT_PUBLIC_GRAPHQL_ENDPOINT non servono: l'endpoint viene da NUXT_PUBLIC_BACKEND_URL. Per le variabili delle immagini e NUXT_PUBLIC_BASE_URL vedi il README di @studio-fes/layer-base.
Cache
-
Driver: con
NUXT_REDIS_URLla cache dati usa Redis; altrimenti il disco (.nuxt-multi-cache) seNUXT_CACHE_ENABLED=true(letta al build, a runtimeNUXT_MULTI_CACHE_ENABLED); altrimenti nessuna cache. -
CDN (Cloudflare): spento di default. Si attiva con
NUXT_CDN_CACHE_ENABLED=true(runtime) oppure nel progetto:export default defineNuxtConfig({ runtimeConfig: { cdnCacheEnabled: true }, })Con la cache dati attiva, le query emettono
CDN-Cache-Control: max-age=300, stale-while-revalidate=86400eCache-Tag, che il plugingraphql-cdn-headersporta sulla pagina e sul suo_payload.json; anteprime, errori e 404 sonoprivate.Cache-Tagรจ separato da virgole, come vuole Cloudflare (pluginserver/plugins/cloudflare-cache-tags.ts). Il resto รจ fuori dal layer, in Craft (craft-toolkit โ Toolkit โ Cloudflare): lรฌ si accende il purge per tag, Check edge cache verifica header, proxy e Cache Rule, e Create Cache Rule crea la regola via API (serve un token con "Zone โ Cache Rules โ Edit"; altrimenti la regola si crea a mano sul dashboard Cloudflare). Senza proxy Cloudflare gli header vengono ignorati. -
server/multiCache.serverOptions.tsva nel progetto: nuxt-multi-cache non lo cerca nei layer. Basta riesportare quello del layer:export { default } from '@studio-fes/layer-craft/server/multiCache.serverOptions' -
Le API di purge (
multiCache.api, con il token) restano nella config del progetto.
Components
<CraftImage>
Optimized image component for Craft CMS assets with transforms.
<template>
<CraftImage
:image="entry.image"
:transform="{ width: 800, height: 600 }"
alt="Image description"
/>
</template>
Composables
useCraftEntry()
Fetch Craft entries with caching and error handling.
const { data: entry, error } = await useCraftEntry({
slug: route.params.slug,
section: 'pages'
})
useCraftSEO()
Generate SEO metadata from Craft entry data.
const { title, description, image } = useCraftSEO(entry)
useHead({
title: title,
meta: [
{ name: 'description', content: description }
]
})
useCraftOrgSchemaIdentity()
Registers the site-wide schema.org identity from the Craft Toolkit plugin's Org Schema CP panel (orgSchema(site: ...) query) โ Organization, LocalBusiness, MedicalClinic, Person, etc. Typically called once, from an app-level plugin:
// app/plugins/org-schema.server.ts
export default defineNuxtPlugin({
name: 'org-schema',
enforce: 'pre',
dependsOn: ['nuxt-graphql-middleware-provide-state'],
async setup() {
const { data } = await useGraphqlQuery('OrgSchema', { site: 'default' })
useCraftOrgSchemaIdentity(data.orgSchema)
},
})
For a LocalBusiness-like identity with multiple addresses (a multi-clinic/multi-branch business), this registers one schema.org node per address โ matching Google's guidance for multi-location businesses โ each with its own priceRange/openingHours.
useCraftOrgSchema()
Registers an entry's Org Schema field pieces (Article, Product, Event, MedicalClinic, ...) as schema.org nodes:
const page = await useCraftEntry<PageEntryFragment>({ slug: [route.params.slug] })
useCraftSEO(page)
useCraftOrgSchema(page)
This expects the entry query to select the field's json property (the piece pre-merged with its @type, ready to parse):
fragment OrgSchemaFieldPieces on ToolkitOrgSchemaFieldPiece {
json
}
fragment PageEntry on pageDefault_Entry {
...EntryInterfaceType
orgSchema {
...OrgSchemaFieldPieces
}
}
GraphQL Queries
Place your .gql files in the queries/ directory:
# queries/Entry.gql
query Entry($slug: [String]) {
entry(slug: $slug) {
id
title
... on pages_page_Entry {
content
seoTitle
seoDescription
}
}
}
Use them in your components:
const { data } = await useAsyncQuery(Entry, {
slug: route.params.slug
})
Server Utils
createCacheTags()
Generates the cache tags (data cache + CDN Cache-Tag) for every cacheable GraphQL query. By default it adds:
- the operation name (
Entry,Entries, โฆ) entry:<id>for every entry returnedarchive:<sectionHandle|typeHandle>for every entry returned byEntries
Extending the tags: cacheTags.serverOptions.ts
Don't override the util: create server/cacheTags.serverOptions.ts in the project (same convention as graphqlMiddleware.serverOptions.ts) and return the extra tags from extendTags.
// server/cacheTags.serverOptions.ts
export default defineCacheTagsServerOptions({
extendTags({ operationName, result }) {
const entry = result?.data?.entry
// archive pages: tagged with the archived sections, so they get purged
// together with the `Entries` queries of the same sections
if (operationName === 'Entry' && entry?.__typename === 'pageArchive_Entry') {
return [entry.archiveType].flat().filter(Boolean).map((type: any) => `archive:${type?.handle ?? type}`)
}
},
})
extendTags receives { operationName, result, tags, event }, can be async and can also remove tags by mutating tags. Files found in every layer are applied in order (layers first, project last). Restart the dev server after creating the file.
Preview Mode
The layer includes automatic support for Craft's preview tokens. Preview requests are handled automatically through the preview plugin.
Configuration
The layer extends the base configuration with:
{
modules: [
'nuxt-graphql-middleware',
'nuxt-multi-cache'
],
graphqlMiddleware: {
serverApiPrefix: '/gql',
clientCache: {
enabled: true
},
autoImportPatterns: ['./queries/**/*.gql']
}
}
Development
# Install dependencies
pnpm install
# Develop with playground
pnpm dev
# Download GraphQL schema
# Set graphqlEndpoint in nuxt.config.ts, then run:
pnpm dev
# Build
pnpm build
# Lint
pnpm lint
Example Usage
See the Craft starter for a complete implementation.
Extending
You can further customize this layer:
export default defineNuxtConfig({
extends: ['@studio-fes/layer-craft'],
graphqlMiddleware: {
downloadSchema: 'dev-only',
clientCache: {
enabled: true,
maxAge: 1000 * 60 * 5 // 5 minutes
}
},
runtimeConfig: {
multiCacheEnabled: true
}
})
License
MIT
Related Packages
Nuxt module for iubenda Cookie Solution โ locale-aware config, script loading vi...
Base Nuxt layer with shared utilities, components, and configurations for buildi...
Interactive wizard to scaffold a new project from the studio fes Nuxt layers sta...
Version History
| Version | Released | License |
|---|---|---|
| 0.5.2 | MIT | |
| 0.5.1 | MIT | |
| 0.5.0 | MIT | |
| 0.4.11 | MIT | |
| 0.4.10 | MIT | |
| 0.4.9 | MIT | |
| 0.4.8 | MIT | |
| 0.4.7 | MIT | |
| 0.4.6 | MIT | |
| 0.4.5 | MIT | |
| 0.4.4 | MIT | |
| 0.4.3 | MIT | |
| 0.4.2 | MIT | |
| 0.4.1 | MIT | |
| 0.3.11 | MIT | |
| 0.3.10 | MIT | |
| 0.3.9 | MIT | |
| 0.3.8 | MIT | |
| 0.3.7 | MIT | |
| 0.3.6 | MIT | |
| 0.3.3 | MIT | |
| 0.3.2 | MIT | |
| 0.3.1 | MIT | |
| 0.3.0 | MIT | |
| 0.2.18 | MIT | |
| 0.2.17 | MIT | |
| 0.2.16 | MIT | |
| 0.2.15 | MIT | |
| 0.2.14 | MIT | |
| 0.2.13 | MIT |
Showing the latest 30 of 47. See every release on npm