keyboard_backspaceGo back

Migrating Astara Play from NuxtJS2 to NuxtJS3

April 16

Until not so long ago, we were running our Astara Play with NuxtJS 2. While it served its purpose, the truth is that it was outdated, and we had some issues in the code that we needed to address to launch a new version.

This blog shares how painful, or not, the migration of our code from NuxtJS 2 to 3 was.

Our origins

Astara Play 2 used several modules from the NuxtJS community. We created a PWA with the nuxtjs module and several other utilities, like Capacitor, to access HTML5 capabilities like GPS from the phone. In the long run, we aimed to create an app for Android and iOS using Capacitor, but things changed quickly, and that's for another story.

The project launched as an experiment. We were creating and exploring different product ideas, and we used the code to iterate and work on it, adding new things and functionalities without thinking twice. The aim was to release early and often so we could iterate and test new ideas as quickly as possible.

While it sounds good, over time, we added some complexity to the code (as you know, entropy is the only thing that constantly grows, and this project was no exception). If we add to the mix that the team behind NuxtJS 2 released NuxtJS 3, we quickly realize that it is time to rebuild the project entirely. However, we will use just NuxtJS and a few other tricks, which we will explain below.

Starting with NuxtJS3

NuxtJS 3 gives you the option to start from scratch using Typescript. I don't want to enter into the battle of whether this is too much for a small project and team like us, but the truth is that we wanted to try it for real and embrace the types that we have been missing deeply all these years (we code basically in Python, but "deep" in our hearts, we all love types).

We had never worked with Typescript before, so we decided to go with it step by step, adding more types as we grew and skipping some of the recommendations.

Splitting complexity from code into npm packages

The previous project was a monolithic solution: everything was there, from components to small JS libraries that could directly import all the inlined CSS you can imagine (scoped, of course!). While this worked, the truth was that over the years, we have seen how to reuse some parts of our code in other projects, and there were better solutions than copying and pasting. It worked, but in the end, you had to maintain several projects simultaneously, making your fast coding a slower process over time.

For these reasons, we moved all the API logic into a pure Typescript library and moved all the CSS we needed to another. The perfect case scenario would be that we should not write any CSS in any vue component and that our components will be just presenting data to our clients. Easy peasy, right? Well, kind of 🙂

In addition to this solution, we use conventional commits for releasing and tagging our versions, so with every deployment, we have a versioned package that you can pin in your different projects without breaking changes if needed. This gives us the flexibility we were looking for, as we can release new versions and update other projects when we can without forcing everyone to move forward simultaneously.

Storybook, CSS, and pure HTML

When we switched to this model, we wanted to make the CSS and components agnostic to any frontend framework: React, Svelte, or Vue (choose your poison). The best solution was to use Storybook, so our product and design team could validate and verify our components using only vanilla JS and plain HTML.

This approach has forced us to think twice before creating any component. The component will have no interaction, so we would only build the atoms in the CSS that we could reuse later in our NuxtJS project to build the components. By forcing us to do it this way, we have created a composable CSS library that ideally fits our project needs and could be reused in any third-party framework without any problems.

This solution solves the problem of reusing code across our projects. From this moment, we don't need to copy and paste; we only need to install the CSS package, and we can use it in any frontend framework.

If you have read this far, you might wonder if we decided to write all our CSS from scratch. Well, the answer is no. We leaned towards Tailwind CSS. This library is fantastic for two reasons: you can customize it as much as you need it, and it forces you to use only the CSS you need, building the most minimal CSS for your project.

With these tools in place, we created our stories for Storybook, and we ended up with 22 stories ranging from buttons to animations, as well as flex layouts and typographies.

Accessing our APIs with a Typescript package

The next step in the process was to move all our logic into a typescript library. By doing so, we could isolate the logic in a single package, build types, test without taking care of any UI elements, and be sure that our NuxtJS project can use it without problems.

The package is relatively small, and thanks to using TypeScript, when you code, you get autocomplete methods, objects, etc., out of the box, bringing a better developer experience to our developers within the team.

As with the previous package, we use conventional commits and releases using SEMVER to pin versions and use the version we need in every project.

Publishing private packages

While all this might sound fantastic, we needed to do this in private. We cannot publish the packages for others to use (for now), so we needed a place to publish the packages and work with npm as usual.

The solution was to use the GitHub NPM registry. The best part is that we have automated and integrated with GitHub actions everything:

  • Testing
  • Linting
  • Building package
  • Publishing package

The developers sign all the code, so we know who has done what. Publishing the packages to our GitHub organization is lovely, as we can share them with any developer within the organization. We can also release them whenever needed, as other projects could upgrade when they want.

With these two packages ready to use, writing the new components in NuxtJS3 was easy.

Using our CSS and API packages

Installing the packages from GitHub is easy. You must create a Personal Access Token so others can access and install the registry. This enables us to deploy the front end in Vercel without problems. We only have to rotate the token, and we are good to go.

Adding the libraries is as easy as editing the package.json. You cannot install them via npm because, by default, npm will search in its registry, but by adding the package by hand into the project and adding the file .npmrc where you specify the registry for your packages as well as the PAT token. With this, you are ready to go. If you use zsh or something similar, you can save your PAT token in a .env file, and when you access the code folder, it will be exposed for your session so that npm will work out of the box.

Upgrading the version needs to be done manually. Again, it's not a big deal because, in this way, you can always pin the version that you want.

After a few months of working like this, we are delighted as we can now fully work on parallel adding new business logic into the API clients, as well as creating new CSS classes that will help us with the design and develop the frontend components without interrupting each other.

One of the most exciting aspects for me has been knowing that when a class doesn't exist, you still code the component, adding the class. It will not look perfect, but when the people coding the CSS finish it and publish a new version, my component will automatically look beautiful without having to write a single line of CSS myself. This is wonderful. Moreover, fixing the CSS or the code has been much more accessible because usually, the fixes are done in the low-level packages and not in the components, so solving it there solves it everywhere.

Final thoughts

The migration was done in about a month, with two people working together. NuxtJS 3 is much better than NuxtJS 2, and using Typescript has benefited us because we have managed to add auto-complete solutions to our coding editors without hassle. Splitting complexity into their modules has been one of the best decisions we have made in the migration, and we will keep things like this from now on in all our projects (not only front but also back code).

I hope you have enjoyed this long post. If you do, please share it and follow us on our social networks!

Daniel Lombraña González