DEV Community

Cover image for Journey to Svelte (through Gatsby)
Patryk Staniewski
Patryk Staniewski

Posted on

Journey to Svelte (through Gatsby)

Our journey began with… Gatsby, but let’s start from the beginning.

We received designs from our UI team, project manager created stories for a new project - a promo site for a new product - and we started discussing technologies. We’ve decided to go the safest route and went for Gatsby - React static site generator. It gives a lot - image optimizations, themes, plugins, GraphQL API, you name it.

We have created our atomic components, made a static homepage, and connected it to our API. Next, we’ve added the rest of the pages, such as various policies and user agreements, about us, pricing, and so on.
After that, there was a design and QA review, and they reported their feedback. We’ve made fixes, adjusted our tests, and voilà, a website was born. Slowly we’ve been adding small, simple features while polishing our actual product - a collaborative content studio platform that empowers PR & IR professionals.

And then one day, almost a year later, our planning session started. New business stories were presented - we were to add "News Hub" which would be a list of articles that we can click through with search, filters, and paginations, and a single article view with an ability to share articles on social media. Nothing extraordinary but as with almost all related sites, it had to be prerendered to have a satisfactory SEO experience. Since we didn’t know about it at the beginning, we didn’t prepare our stack accordingly hence we needed to figure out how to achieve server-side rendering in Gatsby.

It was a real brick wall. Even if we were to compromise SEO, we weren’t able to make dynamic metatags that are needed for social media, such as title, description, og:image, og:url and so on that are needed before our JS could finish rendering the page.

Finally, after few days of fighting with it, we decided we need to look for another way to avoid the Sunk Cost Fallacy. We went to the drawing board and discussed our options. The most obvious one was NextJS which is another React framework, but what differentiates it from Gatsby is that it can handle both SSR and SSG. We spent a few hours replacing configuration files, moving files around, and renaming our environmental variables. The site was up and running on our develop tier, but after running performance tests, we found out that it was a lot worse than before.

lighthouse performance screen after migrating to nextjs

We understood that more changes were needed to match standard NextJS performance that usually reaches upwards of 80 in LH. But how many changes? How much time would we need? These were difficult questions to answer at that time and frankly, we weren’t exactly looking forward to profiling individual components to increase our score.

By that time, we had some troubles with virtual dom itself in our custom rich text editor that we based on slate - it was getting a bit laggy when creating huge financial documents (they usually have enormous tables and a lot of infographics) -so we were already thinking about other options and that’s where svelte comes into the light - especially sapper which was de facto default framework to be used with svelte at that time (SvelteKit wasn’t even announced).

We didn’t have any plans for that weekend anyway so we decided to try it out in our own free time, because well, why not. It turned out to be a lot easier than we were expecting (although not as trivial as one would hope).

First of all, we started by creating a separate branch and run rm -rf *. After a few commands and we were greeted by a sapper page.

npx degit "sveltejs/sapper-template#webpack" .
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

We got familiar with folder structure and get to work.

Our components in React were based on Styled System, which we had huge success with previously. It let us quickly build components with responsive styles based on scales defined in our theme object. Under the hood, it uses Emotion to create dynamic styles on page creation.

Our components looked something like this:

export const ContentHeader: FC = ({ children }) => (
  <Flex
    sx={{
      alignItems: 'center',
      justifyContent: 'center',
      mb: [3, 4],
      bg: 'secondary',
      color: 'white',
    }}
  >
    <Text as="h1" sx={{ fontSize: [5, 6] }}>
      {children}
    </Text>
  </Flex>
)
Enter fullscreen mode Exit fullscreen mode

Although Svelte supports CSS-in-JS and we could theoretically make it work, we thought it would be better to keep it simple. We added one of the most popular CSS framework to our setup Tailwind CSS, adjusted tailwind.config.js according to our previous theme, and started converting our components.

<div class="flex items-center justify-center mb-4 text-white bg-secondary lg:md-8">
  <h1 class="text-3xl lg:text-5xl">
    <slot />
  </h1>
</div>
Enter fullscreen mode Exit fullscreen mode

49% fewer characters were used to create the same component (even better than Rich has promised us in Write less code). But maybe it works great for simple, presentational components. What about more advanced examples like components with custom hooks and refs?

// REACT

export const useClickOutside = (ref: RefObject<HTMLElement>, callback: (node: HTMLElement) => void) => {
  useEffect(
    () => {
      const listener = (event: MouseEvent) => {
        if (!ref.current || ref.current.contains(event.target)) {
          return;
        }
        callback(event);
      };

      document.addEventListener("click", listener, true);

      return () => {
        document.removeEventListener("click", listener, true);
      };
    },
    [ref, callback]
  );
}

// Usage
const ref = useRef<HTMLElement>(null);
useOnClickOutside(ref, () => {})

return {
  <div ref={ref} data-testid="dropdown" />
}
Enter fullscreen mode Exit fullscreen mode
// SVELTE

export const useClickOutside = (node: HTMLElement, callback: (event: MouseEvent) => void) => {
  const listener = (event: MouseEvent) => {
    if (node && !node.contains(event.target) && !event.defaultPrevented) {
      callback(event);
    }
  };

  document.addEventListener("click", listener, true);

  return {
    destroy() {
      document.removeEventListener("click", listener, true);
    },
  };
};

// Usage
<div use:useClickOutside data-testid="dropdown" />
Enter fullscreen mode Exit fullscreen mode

Another win for Svelte, this time by around 35%. The pattern continued with all of our components, hooks, and pages. Our codebase shrunk by just a shy of 45% characters, was easier to read and maintain.

It took the two of us about 20 hours to complete the rewrite. We’ve made the finishing touches on Monday and ran our tests again on develop tier.

lighthouse performance screen after rewrite

Performance on our develop tier matched our production which has additional optimizations like CDN and caching.

We presented our results, send the project to our QA team for another session, and in less than our two-week sprint - our website was redeployed and ready to add additional pages and functionalities - ready for SSR-ed articles for the world to see.

Top comments (0)