In 2020, I released my first boilerplate for building SaaS applications, netcoresaas.com. It included a Vue2 frontend with a .NET backend.
Not until 1 year later, I was able to migrate from Vue2 to Vue3, Vue3 to React and React to Svelte.
Introducing SaasFrontends v1.0.
--
As they say, If it were easy, everyone would do it.
Challenges:
- Creating the Boilerplate
- Migrating Vue2 to Vue3
- Migrating Vue3 to React
- Migrating React to Svelte
- Creating the Documentation
- Creating free UI Components
- Creating the Editions
- Publishing Demos
- Publishing Codebases
- Publishing on Gumroad
- Creating the Blog
1. Creating the boilerplate
Back in 2020, I had just finished a project which consisted in migrating a 4GL to a desktop .NET application. I decided it was time to move it to the web, but I had zero knowledge in JavaScript and CSS.
I figured it would be nice to migrate my Desktop App to the Web with the mindset of making it a boilerplate for other devs like me. That led me into testing JavaScript frameworks and when I tried out Vue (v2), I immediately loved it.
I was thinking of using Bootstrap, since it has most common UI components, but I read about Tailwind CSS, and it honestly changed the whole picture for me, I never liked raw CSS. I bought Tailwind UI Marketing + Application package (you should get it), and started to learn with it.
They don't have Vue2 components so it was a bit of a challenge when I tried to use functional components.
It took me 3 months of development + 3 months of marketing, and the end product was netcoresaas.com, my first web product.
You can read more about this here.
2. Migrating Vue2 to Vue3
I got a about 20 requests of the updated Vue version, and back in January 2021, I created a branch to try to migrate vue2 to vue3 as fast as possible, but I failed.
I knew I had to rewrite all of the components by hand (with the help of Find and Replace of course).
Before I started migrating, I decided to use Vite, since it was also created by Evan You the creator of Vue, and because Vite supports React and Svelte.
2.1. Component Definition
In Vue2, if you want TypeScript support, you need to make your components as Class Components:
<template>
...
</template>
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
@Component({...})
export default class SampleComponent extends Vue {
counter: number = 0;
mounted() { ... }
...
}
In order to convert them to Vue3 learned that the best way was to use the new Script Setup sintax:
<template>
...
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
const counter = ref<Number>(0);
onMounted(() => { ... })
...
</script>
2.2. Reactive Variables
Vue3 reactive variable syntax makes more sense to me, since you always know when updating the value using the .value
property. And also inside the <template>
, Vue3 knows which variables are reactive.
// <template> ...
<div>Counter: {{ counter }}</div>
// <script> ...
...
const counter = ref(0);
function increment(i: number) {
counter.value += i;
}
2.3. Computed Functions
In Vue2 you need to put your computed functions inside computed:
, or if your using TypeScript with a getter
propery:
get fullName() {
return firstName + ' ' + lastName;
}
There's no need for that in Vue3, since functions know when they're using a reactive variable:
const firstName = ref<string>("Alex");
const lastName = ref<string>("Martinez");
fullName() {
return firstName + ' ' + lastName;
}
2.4. Template Refs
When you want to call a function inside a child component, and have the TypeScript autocompletion, you do it this way:
// Template
<LoadingButton ref="loadingButton" @click="start()">Loading</LoadingButton>
// Script
$refs!: {
loadingButton: LoadingButton;
}
start() {
this.$refs.loadingButton?.start()
}
In Vue3 you need to use an InstanceType
object of your type:
// Template
<LoadingButton ref="loadingButton" @click="start">Loading</LoadingButton>
// Script
const loadingButton = ref<InstanceType<typeof LoadingButton>>();
function start() {
loadingButton.value?.start()
}
2.5. Updating packages
These were the main packages that needed to be upgraded:
3. Migrating Vue3 to React
I copy-pasted all the Vue2 views and components to the React project. But as I tried to rewrite the components, I found out that Vue3 seemed more like React.
3.1. Component Definition
We've seen the Vue3 component structure, now take a look at how React does it:
import { useEffect } from "react";
export default function SampleComponent() {
...
useEffect(() => {
console.log("mounted");
}, []);
return (<div></div>);
}
3.2. Store
The hardest thing to copy-paste and fix was the Store. React uses reducers, whereas Vue3 uses a vue-specific library.
3.3. Reactive Variables
React uses Hooks, which seemed so strange to me, at first. Once you get the grasp of it, it's so obviously effective.
const [counter, setCounter] = useState(0);
Every function that references reactive variables, will be called and the UI will re-render.
3.4. Meta-tags
Another thing that changed completly was URL meta tags. React has a ton of libraries for everything, but I ended up using react-helmet
.
You can read more about meta-tags here.
3.4. Template Refs
I didn't quite like the React-way to declare child components (if you want to call its functions):
export interface RefLoadingButton {
start: () => void;
stop: () => void;
}
...
const LoadingButton = ({ disabled, children, onClick }, ref: Ref<RefLoadingButton>) => {
const [loading, setLoading] = useState(false);
useImperativeHandle(ref, () => ({
start,
stop,
}));
function start() {
setLoading(true);
}
...
}
export default forwardRef(LoadingButton);
Calling a child component method:
const loadingButton = useRef<RefLoadingButton>(null);
function start() {
loadingButton.current.start();
}
return (
<div>
<LoadingButton ref={loadingButton} onClick={() => start()}>
Loading
</LoadingButton>
<div>)
4. Migrating React to Svelte
Svelte is basically React, so the logic thing to do was to copy-pase the components into the Svelte project and fix them.
4.1. Component Definition
I implemented Svelte components into 2 parts, script and html. All styles are Tailwind CSS utilities.
<script lang="ts">
import { onMount } from "svelte";
onMount(() => { ... });
...
</script>
<div>
...
</div>
4.2. Reactive Variables
Every variable is reactive, and there's a simpler way to declare component properties:
// property
export let title: string;
// optional property
export let description: string = "";
// reactive variable
let counter: number = 0;
4.3. Computed Functions
If you want a function to be called when the reactive variable changes, you need to prefix it with $:
:
$: discountedPrice = (): number => {
if (!selectedPrice) {
return 0;
}
return selectedPrice.price * 0.8;
}
4.4. Template Refs
Svelte has the simplest template-refs sintax. You only need to export the properties and methods that will be accessed by a parent component:
<script lang="ts">
...
let loading: boolean = false;
export function start() {
loading = true;
}
</script>
...
And use the child component:
let loadingButton: LoadingButton;
function start() {
loadingButton.start();
}
...
<LoadingButton bind:this={loadingButton} on:click={() => start()}>Loading</LoadingButton>
5. Creating the Documentation
I needed a website where users can discover the templates, so I took the Vue2 SaasFrontend and started coding:
- Landing
- Docs
- Blog
Honestly I wasn't happy with the result, specially because I wanted to write in mdx sintax to showcase the UI components and to write more blog posts.
I found out that the tailwindcss.com documentation uses Next.js and is open source, although it has no MIT license. So I cloned it, deleted all I didn't need, redesigned it and, started writing.
I hope redesigned the website enought to be considered fair use. If I did not, I will have to write this Next.js site from scratch.
6. Creating Free UI Components
Since I created the same app in 4 frameworks/libraries, I ended up having a small UI component library:
- Buttons
- Banners
- Modals
- Tabs
- ...
So it occurred to me that I needed a /components section on this website.
You can preview, copy, and download 13 components in Vue2, Vue3, React and Svelte.
7. Creating the Editions
Finally, today (January 16th, 2022), I got to put a price on my product. It's one of the hardest parts, but at least I knew:
- I wanted to have at least one edition as open source, so devs/designers could browse my code.
- I wanted to have a sandbox codebase (no API).
- I wanted to have a full stack codebase.
The end result was:
Edition | Price | Features | Vue2 | Vue3 | React | Svelte |
---|---|---|---|---|---|---|
Starter | Open source | Only front pages | → | → | → | → |
Sandbox | $19 usd/framework | 1 frontend (+30 pages) | → | → | → | → |
Essential | $299 usd | 1 frontend + .NET API | → | → | → | → |
Premium | $499 usd | 1 frontend + .NET API + Team license | → | → | → | → |
8. Publishing Demos
For each edition, I wanted to make a demo. So if there are 4 frontends and 4 editions, I had to make 4 x 4 = 16 demos.
Good thing Essential and Premium are the same codebase, so the end result was:
The Starter edition codebases have their own repo, and they are published to Vercel.
The Sandbox/Essential/Premium edition codebases belong to the same repo, but on the following branches:
- vue2-sandbox-demo
- vue3-sandbox-demo
- react-sandbox-demo
- svelte-sandbox-demo
- vue2-essential-demo
- vue3-essential-demo
- react-essential-demo
- svelte-essential-demo
With each Sandbox commit to production, it publishes to Vercel.
For the Essential codebases eployments are manually made to a AWS Lightsail IIS server.
9. Publishing Codebases
Once I was happy with the demos, I created the following branches:
- vue2-sandbox-codebase
- vue3-sandbox-codebase
- react-sandbox-codebase
- svelte-sandbox-codebase
- vue2-essential-codebase
- vue3-essential-codebase
- react-essential-codebase
- svelte-essential-codebase
Each one of them has customizable environment values, such as:
- PRODUCT_URL: your website URL (without https or slashes)
- DEVELOPMENT_STRIPE_PUBLIC_KEY
- DEVELOPMENT_STRIPE_SECRET_KEY
- PRODUCTION_STRIPE_PUBLIC_KEY
- PRODUCTION_STRIPE_SECRET_KEY
- And more...
10. Publishing on Gumroad
I decided to use Gumroad instead of Stripe, because now I could get reviews. On netcoresaas.com I implemented my own way of giving my customers the codebase.
My Gumroad Products:
11. Creating the Blog
Finally, this blog post.
It took me all day to write this. I hope it was useful to you in some way.
--
Let me know what would you like me to write about.
Top comments (0)