DEV Community

Cover image for How to optimize your Storyblok app
Dušan Perković
Dušan Perković

Posted on

How to optimize your Storyblok app

If you are using @storyblokcom with NextJS, and following the official guides from Storyblok, you know that the usual way to start the project is:

  1. Create a NextJS project using create-next-app
  2. Install the necessary dependencies to integrate with Storyblok.
  3. Create your Storyblok components.
  4. Initialize the NextJS - Storyblok connection by adding the following code to your _app.jsx.
...

import { storyblokInit, apiPlugin } from "@storyblok/react";
import Feature from "../components/Feature";
import Grid from "../components/Grid";
import Page from "../components/Page";
import Teaser from "../components/Teaser";

const components = {
  feature: Feature,
  grid: Grid,
  teaser: Teaser,
  page: Page,
};

storyblokInit({
  accessToken: "your-preview-token",
  use: [apiPlugin],
  components,
});

...
Enter fullscreen mode Exit fullscreen mode

The problem:

You are loading all of your components on each page load!

This is because the contents of _app.jsx get executed on every page. So all the JavaScript that is imported in the _app.jsx file, gets bundled and sent to the client on every page load.

Since the official guide recommends importing all of your components in the _app.jsx file, this means that all your component code get loaded on every page. Even if that page is completely static and doesn't use any StoryBlok components at all!

I ended up creating a Github issue, and implementing this feature via a PR, on the official @storyblok/react Github repo. I was a bit hesitent about contributing, but the good folks at the @storyblok/react repo were very kind and accepting of it, so a big thanks to them!

How to load only the stuff you need

Instead of loading all your components in the _app.jsx file, just initialize the connection, and remove any component imports you have, so it looks more like this:

...

import { storyblokInit, apiPlugin } from "@storyblok/react";

storyblokInit({
  accessToken: "your-preview-token",
  use: [apiPlugin],
});

...
Enter fullscreen mode Exit fullscreen mode

Then, in your page components, use the new setComponents method, to set only the components you need on that page. So it would look something like this:

...

import { getStoryblokApi, StoryblokComponent, setComponents } from "@storyblok/react"
import Feature from "../components/Feature";
import Grid from "../components/Grid";
import Page from "../components/Page";
import Teaser from "../components/Teaser";

export default function Home(props) {
    const story = props.story
    setComponents({
      feature: Feature,
      grid: Grid,
      teaser: Teaser,
      page: Page,
    })

    return (
      <div className={styles.container}>
        <Head>
          <title>Create Next App</title>
          <link rel="icon" href="/favicon.ico" />
        </Head>

        <header>
          <h1>
            { story ? story.name : 'My Site' }
          </h1>
        </header>

         <StoryblokComponent blok={story.content} />
      </div>
    )
  }

...
Enter fullscreen mode Exit fullscreen mode

That's it!

Now, when NextJS builds your page, it will only include the components imported directly on that page and reduce your JS bundle size per page, improving your SEO and speeding up your page load times.

Latest comments (2)

Collapse
 
dartess profile image
Kozlov Sergey

Hey! Nice point!
I'll just try to improve your solution a little.
In your case, react will call setComponents function for each render. It's fair that this can rarely happen (depending on your logic). But still it can be.
Therefore, you can make a primitive hook so that the call occurs only once on the first render:

import { useState } from 'react';
import { SbReactComponentsMap, setComponents } from '@storyblok/react';

export function useComponents(components: SbReactComponentsMap) {
  useState(() => setComponents(components));
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
noblica profile image
Dušan Perković

Hey Kozlov, thanks for the reply! Appreciate the contribution :)

If I understand you coorectly, I'm guessing we could achieve a similar thing by wrapping the function with useCallback?