Setting up Storybook on an Astro project

by Matt Fantinel
27 Apr 2025 - 7 min read

I really, really thought this was gonna be easy. But with front-end development, are things ever simple?

I've dropped a few hints here and there that I'm redesigning my website and blog. I'm also rewriting it from scratch, with Astro! It's a meta-framework, which means it takes care of the part that handles routing and data-fetching, but stays out of component and reactivity. It's like NextJS or SvelteKit.

The main appeal of Astro, though, is that it is framework-agnostic. You can use it as the base and then build all your components with React, Svelte, Vue, Vanilla JS, or even JQuery if that makes sense for you. It's one of the main appeals of Astro alongside active development, a clear project direction, and the whole zero client-side JS by default philosophy.

My soon-to-be-website uses Astro as its foundation, but keeps Svelte as the framework used for the components, to handle reactivity and just the overall template niceties. It also allows me to just port over existing components from my current SvelteKit website, and it's plug-and-play on Astro. Sweet!

Previously, I've used Histoire, a Vite-based simpler alternative to Storybook. It's fast and really easy to set up, which was the main reason I used it. It's not a fully-featured but I didn't need all the extra features of Storybook anyway. The thing is, development on that one is quite slow and it apparently doesn't work well with Svelte 5 yet. So, I figured I'd set up Storybook itself on my project. It's just a simple collection of Svelte components, right? How hard could it be?

Significantly.

The post below was written from my experience getting frustrated with the lack of proper documentation and a lot of trial and error. I was only able to get things working after a few hours digging into Storybook (and its plugins)'s documentation and source code, and a good deal of arguing with ChatGPT. I'm writing this guide so you don't have to go through all the hoops I did.

Also, I'm using Svelte on my examples as it's the framework I use, but I think the vast majority of this guide could apply to you if you use React, Vue, or none at all.

Understanding the layers

There are a lot of layers to the tech stack here. Astro uses Vite as a build tool to handle dependencies, dev servers, compiling, and whatever else it does. You might have heard of Webpack, which does the same. Vite is basically a newer, leaner version of that.

When you install Svelte (or React, or Vue) on an Astro project, you're actually just installing the Vite integration for that specific framework. Astro doesn't need to know what the client-side framework is because they're pretty much separated.

When you use Storybook, you likely don't want to interact with anything on the Astro side of things. You write stories for your components, and they will all be Svelte/React/Vue files that don't need Astro to work. Which is why there's no "Astro package" for Storybook, or any starting template.

Which means in this case, we want to setup Storybook for a Svelte + Vite project.

Step-by-step

So, with my Astro + Svelte project in hands, these are the first steps I did:

  1. Following the official guide, I ran npm create storybook@latest
  2. Selected "Documentation" as the main use
  3. Even when selecting Documentation, Storybook still installed a lot of unit and visual testing packages that I did not want. So I ran the command below to get rid of all that bloat. You might want to double-check if you actually need one of these packages.
    bash
    npm uninstall @chromatic-com/storybook @storybook/experimental-addon-test @storybook/test @vitest/browser @vitest/coverage-v8 playwright vitest
  4. Deleted the built-in "stories" folder that Storybook adds by default. You can keep this one if you want some pre-built examples.

This looks like it should be it, right? But if you run npm run storybook, you'll see that it gives out an error. The error on the browser is a bit generic but on the terminal you'll see something like:

Internal server error: Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.

Plugin: vite:import-analysis 

File:(...)/node_modules/@storybook/svelte/dist/components/SlotDecorator.svelte

Vite is trying to compile the Storybook code but is erroneously identifying a closing </script> tag as JSX syntax, even though it's a Svelte file. This hints to the fact that the Vite code that is running is not loading the plugin needed to make Svelte work (vite-plugin-svelte). Astro does that automatically, but Storybook does not, as it just assumes a Svelte + Vite project would have that plugin installed as a dependency. So, to fix it, I:

  1. Installed the plugin with npm install --save-dev @sveltejs/vite-plugin-svelte
  2. Added the plugin to the Vite config in my .storybook/main.ts file:
.storybook/main.ts
typescript
import type { StorybookConfig } from '@storybook/svelte-vite';

const config: StorybookConfig = {
	"stories": [
		"../src/**/*.stories.@(js|jsx|ts|tsx|svelte)"
	],
	"addons": [
		"@storybook/addon-essentials",
		"@storybook/addon-svelte-csf",
	],
	"framework": {
		"name": "@storybook/svelte-vite",
		"options": {}
	},
	"viteFinal": async (config) => {
		// Needs to be dynamically imported
		const { svelte } = await import('@sveltejs/vite-plugin-svelte');
		
		config.plugins = config.plugins || [];
		config.plugins.push(
			svelte({
				exclude: ["node_modules/@storybook/**"],
			})
		);
		
		return config;
	}
};

export default config;

And that should be enough for your stories to work!

Example Story syntax with Svelte 5

If, like me, you're struggling a bit to find a good example of the latest syntax for Svelte stories (the older still works though), I found some good ones in the addon-svelte-csf project on GitHub.

Other useful things

Load your global CSS styles on stories

If you have a global SCSS file that your components rely on, you can just add a line to .storybook/preview.ts:

.storybook/preview.ts
typescript
import '../src/styles/global.scss'; // Or whatever your file path is

{...}

Use tsconfig aliases in Stories

If you use aliases set up in tsconfig.json in your components, for example so you can use @components/button instead of src/components/button, you either need to re-configure them for Storybook, or you can reuse the ones from tsconfig instead. To do that, you'll need:

  1. Install this package to allow Vite to understand tsconfig paths: npm install --save-dev vite-tsconfig-paths
  2. Add this to your Storybook's main.ts file:
.storybook/main.ts
typescript
import type { StorybookConfig } from '@storybook/svelte-vite';

const config: StorybookConfig = {
	{...}
	"viteFinal": async (config) => {
		// Needs to be dynamically imported
		const tsconfigPaths = await import('vite-tsconfig-paths').then(m => m.default);
		{...}
		config.plugins.push(
			{...}
			tsconfigPaths({
				projects: ['./tsconfig.json'],
			})
		);
		
		return config;
	}
	{...}
};

export default config;

Support SASS aliases on Storybook

If your project uses aliases for SASS as well, you can alter the viteFinal section of your .storybook/main.ts file to make Vite resolve those aliases as well:

.storybook/main.ts
typescript
import path from 'path';

{...}

"viteFinal": async (config) => {

	{...}
	
	// Add alias for SCSS @styles
	config.resolve = config.resolve || {};
	const stylesPath = path.resolve(process.cwd(), 'src/styles');
	if (Array.isArray(config.resolve.alias)) {
		config.resolve.alias.push({
		find: '@styles',
		replacement: stylesPath
		});
	} else {
		config.resolve.alias = {
		...(config.resolve.alias || {}),
		'@styles': stylesPath
	};
}

Wrapping up

Looking back, it's not really a lot of steps to get it working. But it was a journey to get to this point. Hopefully I'm saving you some time on this!

Both parts of the stack are actually working as they should - Astro does the website part and stays out of the rest, Vite builds the tools, Svelte handles the components, and Storybook renders them. It was just a lack of either clear documentation or better error messages that kept me in the dark for a while. Hopefully there's more effort in improving this in the future!

I don't have comments on my blog, but feel free to shoot me an email if you have questions or suggestions. I'll be glad to help out (or be helped)!

Written by

Matt Fantinel

I’m a web developer trying to figure out this weird thing called the internet. I write about development, the web, games, music, and whatever else I feel like writing about!

About

Newsletter? I have it!

I have a newsletter as another way to share what I write on this blog. The main goal is to be able to reach people who don't use RSS or just prefer getting their articles delivered straight into their inbox.

  • No fixed frequency, but at least once at month;
  • I do not plan on having content exclusive to the newsletter. Everything on it will also be on the blog;
  • I will never, ever ever ever, send you any form of spam.

You might also like

View blog

Automating Social Media Preview Images

6 min read

Social media preview images are very useful if you want to attract people to your website. They're sometimes a pain to create, though. Let's automate it!

Front-End
Read

How I built a blog with Svelte and SvelteKit

14 min read

An overview of the experience I've had using these amazing projects.

Front-End
Read

Fantinel.dev v5 is here!

10 min read

Out with the green waves, in with the rainbow of pastel colors!

Meta
Read

Progressive Enhancement (and why it matters)

8 min read

Progressive Enhancement isn't just another web jargon; it's a guiding principle shaping modern web development.

Front-End
Read