Building a reusable frontend stack for Iguana Solutions#
In short#
When I joined Iguana Solutions, the company had a several web projects in the pipeline, but little to no design and frontend development experience. I helped select a set of technologies and methodologies that would fit the company's culture and goals, while also learning a lot about builing Single Page Applications in the process.
The client#
Iguana Solutions is a full fledged hosting provider, offering consulting, DevOps solutions, managed cloud services and security audits. The company's customers include recognize groups and brands like Deezer, Easybourse, Canal+, H&M, among others.
The problems#
When I joined the company had many software ideas to further serve its clients, but not a lot of experience in web development. Members of the engineering team could build internal dashboards to monitor the client's projects, but nothing that could be used by clients or partners.
The first step was to interview the CTO and team members to learn about their tech habits and tastes, the resources available, and select a set of frontend technologies that could fit.
It became clear rapidly what we were going to build would require a big step up in visual design, interactivity, state management, and overal quality.
The solutions#
My directives from the CTO for the frontend technical stack were:
- Cloud compatible and hosted on AWS, with the rest of the projects.
- Client side application to avoid managing a web server.
- Easy to understand and modify, onboarding and iterations should be fast. Ideally, our backend developer should be able to understand it.
- Performances should be high despite our small team, and batteries included to avoid wasting time assembling a NPM puzzle.
The frontend library#
I had worked with React (pre hooks era), Vue 2 and Angular 1 in the past, but wasn't very interested in them or their more recent versions for different reasons:
- React is notoriously difficult to understand and use. It has huge performant issues. Of course React has a massive ecosystem of plugins (a regular argument from React folks), but buying into them meant being vendor locked, and an even harder learning curve.
- Angular is a complete package, but it felt too much to handle for a single person. It's very opiniated and from what I could tell, lacked in performance (but they are working on it so it may not be an argument anymore).
- Vue was stable, but I needed a complete package with styling, routing, state management, and more. I already had a project made in Nuxt, but at the time they were just starting the beta of their V3, and a future migration wasn't ideal for me.
In the end I picked Svelte, for the followintg reasons:
- It was easy and fast to learn, as it benefits more from HTML/CSS/JS fundamentals than the library knowledge.
- It's compatible with almost all the native JavaScript ecosystem of plugins and libraries, which could help if we needed some.
- It's a complete package: scoped styling, simple reusable data stores, transitions and animations, are all baked in. No maintenance of third parties!
- It does a lot of things for you, among them automatic reactivity to changes without a virtual dom. It's still very performant, as Svelte is a compiler that strips what isn't used and optimizes code.
- It had a framework, (SvelteKit), that was near its V1, so I could easily migrate projects to standardize our code and reduce our third party dependencies.
In the end, Svelte proved to be a good pick and was never a hindrance in the projects. Its data stores were particularly suited for the different jobs we did, which often required syncing a lot of data from different places.
Styling#
To allow a rapid iteration of styles in our projects and still ensure a coherence in styles, I decided to use a mix between several methodologies:
- First, a set of design tokens were created to ensure we have common naming schemes, common styles, and an easy way to change the branding of projects if required.
- Then, a
basestylesheet attached to HTML elements was created using the design tokens. This way, anyone could quickly create a Svelte component with basic HTML elements inside, and obtain an in-brand result. - And finally, utility classes related and non-related to design tokens could be used in components to overwrite and complete existing styles.
I picked Tailwind CSS for the config for the design tokens and the utility classes, and kept its naming conventions, so anyone joining the projects could use the Tailwind docs and get up to speed. The base stylesheet was made with native CSS and the @apply tailwind function to fetch design tokens.
You might have wondered why not create a JavaScript component for each of the HTML elements? To make it short: because it was too much work, clashed with our iteration process, and did not benefit us. Except if they have entirely different functionalities that justified their isolation ; or if they come from or are built for a general purpose agnostic design system (ex: Material Design by Google) ; single HTML elements wrapped inside a JS component, just for styling purposes, are over-engineering.
Put simply: I decided we should bet on HTML and CSS, and extend them with JavaScript through our scoped components when needed.
Visual design#
Unfortunately the designs of the projects I worked on are still under an NDA and cannot be shown here. Without going into specifics, I can share the directions I took with the guidance of my leads.
First, I made a good use of color, transitions, SVG and CSS animations through the projects to create a feeling of playfulness and accessibility for our users. The goal was to find a balance between the austerity of technical tools and the more shiny dashboards managers and directors enjoy.
I also created drag and drop user experiences to allow non-technical users to interact with the content of some of our projects. Other type of pages included multi-steps operations, interfaces with real time updates, etc.
Routing and data fetching#
Routing followed Sveltekit's conventions, one folder = one route. Even if the projects were all SPAs, I still followed the convention of using a layout.ts or page.ts file in each route to fetch data before rendering (this is usually used for server side rendering).
I decided to use this to unify our applications UI rendering. You can use an onMount() life cycle method in svelte pages and components, but it would have resulted in a lot of loading spinners, a spread of the API calls, and a harder management of state. Using the sveltekit convention also allowed me to recover the upper level fetched data in lower pages, using the parent fonction.
For example:
/will check the session and make it available to subpages/org_idwill fetch a list of projects linked to the org and make it available to subpages/org_id/project_idwill fetch project details and make it available to subpages
By enforcing this rule, jumping between projects did not require re-learning anything.
State management#
In single page applications, the state of the application is stored in memory, a hard refresh resets it, so it needs to be reproductable from the APIs.
Most of the projects' states were thought as state machines, and used Svelte stores heavily instead of passing props from a parent to nested components. A change of state from one of the API endpoint, or from the user, created a chain reaction that would change other states, allowing the apps to show what is necessary in each page (texts, popups, images, redirects, etc.).
Stores were organized this way:
- Writable stores were a mirror of each API data endpoint. They served as a source of truth, and were updated when the user changed something (a copy was stored to compare them and indicate unsaved changes in the UI). Their content could directly be sent through a POST/PUT call. They were called core stores.
- Derived stores subscribed to one or several core stores, modified their data to match the needs of the user interface, and sent it to the view. Svelte automatically updated the derived stores when a core store changed, which was an immense productivity boost.
This way, I achieved a one-way data flow: core reflects data, derived transforms core, the UI presents derived, the user modifies the UI, which updates the core, and repeat.
AWS Services#
The projects we created were all AWS centric. I learned about the following services, wrote reusable libraries interfacing with the AWS SDK V3 for our projects, as well as infrastructure as code templates in Cloudformation.
- Amplify
- Cognito
- Xray
- S3
- API Gateway
- Lambda
Lessons learned#
- It's a good time to try new libraries, which are becoming more stable and performant, instead of doubling down on classics like React and Angular.
- You can achieve a good balance between strict design rules and flexibility by leveraging good methodologies and tools.
- Single Page Applications, both in routing and state management, can quickly grow complex and it becomes quickly urgent to follow the standards of the tools to avoid wild implementations.
- AWS services are way too complex, but for sure, they are really useful to kickstart projects.