Logo Learn By Doing Light Mode
NextJS: load, analyse and update (Excalidraw) SVG in Server components
NextJS: load, analyse and update (Excalidraw) SVG in Server components

In this article we describe the technique we have used to load, modify and render SVG (generated by Excalidraw) in NextJS server component, to automatically become responsive and support custom themes

 
techs
 

# Introduction

In this article I describe one of the feature we have developed for this site: the need to integrate diagrams, drawings and sketches.
But I had some requirements to satisfy:
  • theme (color and fonts) support
  • responsive
  • rendered in React server components
This is an example of the result. Change the theme of the site to see it in action:
Diagram Home Another

# Use Photo Editors?

The first idea was to create the images with our favorite editing software and use them in our site. Some of my friends use Photoshop and Illustrator, I use the Affinity suite , others use Gimp . Everyone uses the software they are most comfortable with.
But how to support different themes, in our case dark and light?

We would have to create two images, one for each theme.

Too much work 😅

Two images for themes? Not a great idea!

But even if I created two different images, the solution did not excite me.
In fact, when user changes the theme we should switch the image from the "dark" version to the "light" one, and vice versa.
OK, let's imagine we want to implement this solution. The solutions I had in mind were essentially two:

1. Pure CSS ❌

We use Tailwind and one way to change an image based on theme could be to hide and show them based on current theme:
HTML
The main problem is that now I should load two different images and hide one of the them. Not a great solution for performance and traffic generated. There are other tricks for Tailwind that allow us to switch images but nothing that excited me

2. Client Components ❌

Another easy way to handle this issue is by using a client side approach: we may save the current theme in a global state and make our <Img> component reacts to changes, switching the src image property at runtime and loading the right image when theme changes from dark to light.
 
dark.png <Image src=" PATH " /> light.png
 
However, this solution was also not good, since I would have had to convert our custom server component <Img> that renders images into a client component, losing performance and rendering the images client side, with the risk of delay and flickering

# SVG are vectorial!

The solution that I therefore considered most suitable was to create SVG images in which, being vectorial, I could apply different CSS styles on the svg, path, text and all the other elements that compose an SVG.
The main convenience would have been to create a single image, do only one export and then apply with Tailwind the styles necessary to manage the different themes.

The problem

When we create an SVG image with our favourite tool, we need to define colors for lines, shapes, text and so on. So, after exporting an SVG I would have had to manually replace all the inline styles with the Tailwind classes in order to support both themes, light and dark.
For example, I should update all text svg elements removing the inline style (fill) and replace them with class
HTML
HTML
And I should do the same for all path elements and other SVG elements, manually updating stroke, fill and so on.
Too much work. Again 😅

# Goal

I want to create a React component that loads a local SVG image from the NextJs app router folder (v.14+) and automatically applies colors and fonts based on the theme.
BONUS: make the SVG responsive
Furthermore I wanted it to be done server side, before rendering.
So my wish was to:
  • create a <SVGLoader> component that accept an SVG path: path="img.svg"
  • this component must analyze the content of this svg, process it and replace all the inline styles and attributes generated by the tool with our own custom classes
  • avoid client side stuff: do everything before rendering
But there is still a problem:
I use Affinity Designer , Giorgio, co-author, uses Linux (and I don't know what software he's using 😅), another author might want to use Illustrator or something else.
It would be really complicated to manage all the cases and so I thought that a good strategy would be to use all the same (web-based) tool, which generates a very similar output. So I thought to use Excalidraw , an excellent and popular tool for drawing diagrams and sketching, using a style very similar to our site.

The process

So, the process to create and manipulate the SVG would be the folling:

1. Draw

We draw the image with Excalidraw
Excalidraw demo

2. Export

Excalidraw export
Excalidraw exports to SVGs but they are no responsive and there is no themes support. As you can see in the code snippet below, the svg element has fixed values for its width and height attributes and the path element uses an inline stroke color:
HTML

3. Save / Upload

We save the exported SVG file in our article folder (currently our articles are written using markdown, or rather MDX):
Article Folder

4. Use in MDX files and React / NextJS Components

The MDX article simply use the svg with a dedicated component:
<Excalidraw path="./images/diagram1.svg" />

5. Render the modified image

The SVG will be modified and rendered on server and shown with colors suitable for the dark and light theme:
dark.png <Image src=" PATH " /> light.png
See how it works by clicking on the settings icon at the bottom left and changing the current theme.
 

# The Idea

How to put my idea into practice? We can simplify the process as follows:
 
1. Load SVG Image 2. Analyze & Update SVG 3. Render
 
React
After several tests and experiments the final component should looks like the following:
React
Usage:
Markdown

# Add WebPack SVG loader

The first "problem" is that I couldn't load SVG in NextJS application by default, so I used the @svgr/webpack package and I modified the webpack configuration in next.config.mjs adding a special loader for SVG:
JavaScript
We also modified some presets to prevent the svg from being modified by the loader

# SVG: analyze and update

And now? What strategy to use to parse the SVG and replace the nodes?
First I had to analyze the SVG exported by Excalidraw:
  • Line 3 needs to be modified to support responsive layout: width="100%" height="100%"
  • Line 7: we have to replace fontFace to use our font (IndieFlower)
  • In Line 16 and other svg elements we have to remove the stroke attributes in favor of CSS classes
HTML
After a short search on Google and ChatGPT (😅) I found the library xml-js that allows me to easily parse the HTML structure of the SVG and easily make replaces.
xml-js package convert XML text to Javascript object / JSON text (and vice versa).
And here is the script that loads, parses and updates the SVG before the render:
it's just a simple example to make you understand the concept
TypeScript
So the SVG is modified before the render and now it automatically supports our theme. And it's responsive!
React
Usage:
Markdown
That's all
You can see the result directly in this article. Most of diagrams use our <Excalidraw> component. Change the theme using the icon on bottom left side : )

Did you like this content?

Your feedback is very important to us!
04
Jun
2024