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!
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.
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.
So, with my Astro + Svelte project in hands, these are the first steps I did:
npm create storybook@latestnpm uninstall @chromatic-com/storybook @storybook/experimental-addon-test @storybook/test @vitest/browser @vitest/coverage-v8 playwright vitestThis 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:
npm install --save-dev @sveltejs/vite-plugin-svelte.storybook/main.ts file: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!
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.
If you have a global SCSS file that your components rely on, you can just add a line to .storybook/preview.ts:
import '../src/styles/global.scss'; // Or whatever your file path is {...}
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:
npm install --save-dev vite-tsconfig-paths 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;
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:
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 }; }
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)!
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!
How I built a blog with Svelte and SvelteKit
14 min read
An overview of the experience I've had using these amazing projects.
Fantinel.dev v5 is here!
10 min read
Out with the green waves, in with the rainbow of pastel colors!
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.