30. Storybook. Nuxt + Storybook Setup. Frontend Workshop. Directory Structure in Nuxt
Video version
(Leave your feedback on YouTube)
TL;DR
Storybook is useful for large projects, projects with A/B testing, and projects with multiple themes.
Change the project structure so that all developer code is in a subdirectory. This will help avoid issues with caches, builds, and node_modules.
pathPrefix
is a bad practice in Nuxt component naming, as it complicates refactoring.
export default defineNuxtConfig({
components: [
{
path: '~/components',
pathPrefix: false,
},
],
})
Storing stories and tests alongside components won’t affect the production build.
Store all mock data in separate
.mock
files. This makes it easier to reuse and update.
Storybook encourages you to write pure components, whose state only depends on arguments (properties). This is a good practice.
Storybook Setup for Nuxt
npm i --save-dev storybook @nuxtjs/storybook @storybook/addon-essentials @storybook/addon-interactions @storybook/addon-links
Run Storybook separately from the main build, with configuration that keeps it isolated from the primary project.
{
"scripts": {
"storybook": "IS_STORYBOOK=true storybook dev --port 6006",
"build-storybook": "IS_STORYBOOK=true storybook build"
}
}
Modify the Nuxt configuration depending on the presence of the IS_STORYBOOK
variable:
export default defineNuxtConfig({
pages: !process.env.IS_STORYBOOK,
ogImage: {
enabled: !process.env.IS_STORYBOOK,
},
vue: {
runtimeCompiler: 'IS_STORYBOOK' in process.env,
},
})
pages
- fixes a Storybook navigation bug with the Nuxt route configuration.ogImage
- fixes an SSR bug for the ogImage package, whereSSR = false
by default in@nuxtjs/storybook
.vue.runtimeCompiler
- allows component rendering on Storybook pages. Disable for the main build if it’s not needed.
Entrypoint for launching:
import type { StorybookConfig } from '@storybook-vue/nuxt'
import { mergeConfig } from 'vite'
const config: StorybookConfig = {
stories: [
'../app/**/*.mdx',
'../app/components/**/*.stories.ts',
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-themes',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook-vue/nuxt',
options: {},
},
docs: {},
async viteFinal(config) {
return mergeConfig(config, {
optimizeDeps: {
include: ['jsdoc-type-pratt-parser'],
},
})
},
}
export default config
stories
- directories for locating story files.viteFinal
- fixes a documentation rendering bug.
Configuration file:
import type { Preview } from '@storybook-vue/nuxt'
import { withThemeByClassName } from '@storybook/addon-themes'
import { h, Suspense } from 'vue'
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport'
import './stubs/i18n.stub'
import './stubs/nuxt-link.stub'
const preview: Preview = {
parameters: {
layout: 'fullscreen',
viewport: {
viewports: {
desktop: {
name: 'Desktop',
styles: {
height: '100%',
width: '100%',
overflow: 'clip',
},
type: 'desktop',
},
...MINIMAL_VIEWPORTS,
},
defaultViewport: 'desktop',
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
decorators: [
withThemeByClassName({
themes: {
light: 'light',
dark: 'dark',
},
defaultTheme: 'dark',
}),
(story) => {
return {
setup() {
return () => h(Suspense, {}, [h(story())])
},
}
},
],
}
export default preview
layout
- default layout template for all stories.viewport
- settings for available viewports and global configuration.controls
- automatically identifies control elements for stories.withThemeByClassName
- theme setup.(story) => ... Suspense
- enables stories to wait for dynamic components (e.g., Nuxt Icon).
In .storybook/preview.ts
, connect all global directives, components, and methods.
Adding i18n
for localization:
import { setup } from '@storybook/vue3'
import { createI18n } from 'vue-i18n'
import en from '../../app/locales/en.json'
setup((app) => {
const i18n = createI18n({
locale: 'en',
messages: { en },
})
app.use(i18n)
})
Adjusting the NuxtLink
component:
import { setup } from '@storybook/vue3'
import { action } from '@storybook/addon-actions'
import { h } from 'vue'
setup((app) => {
app.component('RouterLink', {
props: ['to'],
methods: {
onClick(e) {
e.preventDefault()
action('switch-route')(this.to)
},
},
render() {
return h('a', { onClick: this.onClick, href: this.to }, this.$slots.default())
},
})
})
You’ll also need to create stubs for all global methods.