
Fully customizable module for Nuxt 3 & 4 to render the "Blocks" rich text editor element from Strapi CMS.
The implementation is based on Strapi's Blocks React Renderer.
To install the Nuxt Strapi Blocks Renderer module, run the following command:
npx nuxt@latest module add nuxt-strapi-blocks-renderer
npm install nuxt-strapi-blocks-renderer
nuxt.config.{ts|js}:export default defineNuxtConfig({
modules: [ 'nuxt-strapi-blocks-renderer' ]
})
To render text, use the StrapiBlocksText component:
<StrapiBlocksText :nodes="blockNodes" />
In this example, the blockNodes are taken from the JSON response which Strapi provides when using the Blocks rich
text editor element:
<script setup lang="ts">
import type { BlockNode } from '#strapi-blocks-renderer/types';
import type { Restaurant } from '~/types';
const route = useRoute();
const { findOne } = useStrapi();
// Fetch restaurants data from Strapi
const response = await findOne<Restaurant>('restaurants', route.params.id);
// Obtain blocks text nodes from description field
const blockNodes: BlockNode[] = response.data.attributes.description;
</script>
<template>
<!-- Render blocks text -->
<StrapiBlocksText :nodes="blockNodes" />
</template>
To use the useStrapi composable, install the Strapi Nuxt module.
In situations where your project requires specific styling or behavior for certain HTML tags such as <a>, <p>,
and others, you can override the default rendering components used by the Nuxt Strapi Blocks Renderer.
This flexibility allows you to tailor the rendering to align with your project's unique design and functional needs.
First, ensure that your components are globally registered in your Nuxt app. This step is crucial for your custom components to be recognized and used by the renderer.
In your Nuxt configuration (nuxt.config.{js|ts}), add:
export default defineNuxtConfig({
components: {
dirs: [
{
path: '~/components/blocks',
pathPrefix: false,
global: true,
},
// also include ~/components to ensure other local components are resolved properly
'~/components'
],
},
})
!IMPORTANT
It's important to include~/componentsin the dirs array. Omitting it may cause locally scoped components (outside of~/components/blocks) not to resolve correctly.
To customize the rendering of the paragraph (<p>) tag, you need to create a corresponding Vue component.
The name of the component follows a predefined pattern: 'StrapiBlocksText' + [NodeName] + 'Node.vue'.
To override the default paragraph tag, we create a file called StrapiBlocksTextParagraphNode.vue.
<!-- components/blocks/StrapiBlocksTextParagraphNode.vue -->
<template>
<p class="my-custom-class-for-p">
<slot />
</p>
</template>
This component assigns a custom class my-custom-class-for-p to the paragraph tag, which can be styled as needed.
The prefix for the custom components can be adjusted in your nuxt.config.{js|ts}:
export default defineNuxtConfig({
strapiBlocksRenderer: {
prefix: 'MyCustomPrefix',
blocksPrefix: 'MyCustomBlocksPrefix',
},
})
With this configuration, the StrapiBlocksText component becomes MyCustomPrefixStrapiBlocksText and the custom
paragraph node component would be named MyCustomBlocksPrefixParagraphNode.
You can apply similar customizations to all other HTML tags used by the renderer:
Custom heading tags (<h1>, <h2>, <h3>, etc.):
<!-- components/blocks/StrapiBlocksTextHeading1Node.vue -->
<template>
<h1 class="my-custom-class-for-h1">
<slot />
</h1>
</template>
<!-- components/blocks/StrapiBlocksTextHeading2Node.vue -->
<template>
<h2 class="my-custom-class-for-h2">
<slot />
</h2>
</template>
This pattern also extends to the h3, h4, h5 and h6 tags.
Custom list tags (<ol>, <ul> and <li>):
<!-- components/blocks/StrapiBlocksTextOrderedListNode.vue -->
<template>
<ol class="my-custom-class-for-ol">
<slot />
</ol>
</template>
<!-- components/blocks/StrapiBlocksTextUnorderedListNode.vue -->
<template>
<ul class="my-custom-class-for-ul">
<slot />
</ul>
</template>
<!-- components/blocks/StrapiBlocksTextListItemInlineNode.vue -->
<template>
<li class="my-custom-class-for-li">
<slot />
</li>
</template>
Custom blockquote and code tags (<blockquote>, <pre>):
<!-- components/blocks/StrapiBlocksTextQuoteNode.vue -->
<template>
<blockquote class="my-custom-class-for-blockquote">
<slot />
</blockquote>
</template>
<!-- components/blocks/StrapiBlocksTextCodeNode.vue -->
<script setup lang="ts">
const props = defineProps<{
language?: string;
}>();
</script>
<template>
<pre class="my-custom-class-for-pre"><slot /></pre>
</template>
Custom inline text nodes (<strong>, <em>, <u>, <del>, <code>):
<!-- components/blocks/StrapiBlocksTextBoldInlineNode.vue -->
<template>
<strong class="my-custom-class-for-strong">
<slot />
</strong>
</template>
<!-- components/blocks/StrapiBlocksTextItalicInlineNode -->
<template>
<em class="my-custom-class-for-em">
<slot />
</em>
</template>
<!-- components/blocks/StrapiBlocksTextUnderlineInlineNode -->
<template>
<u class="my-custom-class-for-u">
<slot />
</u>
</template>
<!-- components/blocks/StrapiBlocksTextStrikethroughInlineNode -->
<template>
<del class="my-custom-class-for-del">
<slot />
</del>
</template>
<!-- components/blocks/StrapiBlocksTextCodeInlineNode.vue -->
<template>
<code class="my-custom-class-for-code">
<slot />
</code>
</template>
Custom link tag (<a>):
<!-- components/blocks/StrapiBlocksTextLinkInlineNode.vue -->
<script setup lang="ts">
const props = defineProps<{
url: string;
rel?: string;
target?: string;
}>();
</script>
<template>
<a :href="props.url" :rel="props.rel" :target="props.target" class="my-custom-class-for-a">
<slot />
</a>
</template>
When rendering a link tag, the url gets passed as the url component property.
Custom image tag (<img>):
<!-- components/blocks/StrapiBlocksTextImageNode.vue -->
<script setup lang="ts">
const props = defineProps<{
image: any;
}>();
</script>
<template>
<img
class="my-custom-class-for-img"
:src="props.image.url"
:alt="props.image.alternativeText"
:width="props.image.width"
:height="props.image.height"
>
</template>
When rendering an image tag, the image object gets passed as the image component property.
You can also use different image components here, i.e. NuxtImg or others.
To install the dependencies, run the install command:
pnpm install
The project requires Node.js and pnpm to run. You can either install these manually on your system or if you have the nix package manager installed, use the provided nix-shell with the following command:
nix-shell
This will automatically install the needed software and start up a shell.
To generate the type stubs for the nuxt module, run the dev:prepare command:
pnpm run dev:prepare
To start the development server with the provided text components, run the dev command:
pnpm run dev
This will boot up the playground with the default text components.
To start the development server using custom text components, overriding the provided components,
use the dev:custom command:
pnpm run dev:custom
To run ESLint, use the following command:
pnpm run lint
To run the TypeScript type checks, use the following command:
pnpm run typecheck
To run the Vitest unit tests, run the following command:
pnpm run test
To build the module, first install all dependencies and generate the type stubs. Then run the build script:
pnpm run build
The module files will be output to the dist folder.
Releases are automated with uppt, based on conventional commits:
main opens or updates a draft release PR, which bumps the version in package.json and lists
the changes since the last release. Breaking changes result in a major, feat: commits in a minor and all other
commits in a patch release.