Building a SaaS in one week: How I built OnlineOrNot
Tags: saas, react, nextjs
Published: 2021-03-06 21:57:43

---
title: Building a SaaS in one week: How I built OnlineOrNot
published: true
date: 2021-03-06 00:52:00 UTC
tags: ['saas','react','nextjs']
canonical_url: https://onlineornot.com/building-saas-in-one-week-how-built-onlineornot
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/baelgcwmjou6rc02y2ro.png
---

(This is an article from OnlineOrNot.com. You can read the original by [clicking here](https://onlineornot.com/building-saas-in-one-week-how-built-onlineornot).)

When I first started building SaaS apps for side projects, it would take me a solid weekend solely to build the Stripe integration. These days, It's possible to build the whole SaaS app in one week.

**Table of contents**

- [Background Info](#background-info)
- [Tech Stack](#tech-stack)
- [Approach](#approach)
- [Building the SaaS](#building-the-saas)
  - [First, a blog](#first-a-blog)
  - [Uptime Checker Test](#uptime-checker-test)
  - [On Auth](#on-auth)
  - [Using Tailwind for UI](#using-tailwind-for-ui)
  - [On SSR and Emotion](#on-ssr-and-emotion)
  - [Forms!](#forms)
  - [Latency troubles](#latency-troubles)
  - [Docs](#docs)
  - [Stripe integration](#stripe-integration)
- [Shipped!](#shipped)
- [Where to next?](#where-to-next)

## Background Info

Let's start with some background about the app: OnlineOrNot is a pretty standard uptime checker - it does have some fancy logic to ensure the page is _actually_ down before notifying you, but essentially it's a login/sign-up page, a dashboard, a "new page" form, a checker service, a transactional email integration, and a payment integration. I built a similar tool in 2018 (under the same name) to snapshot test GraphQL queries, but that project never took off.

I recently [migrated my blog from Gatsby to Next.js](https://maxrozen.com/walkthrough-migrating-maxrozen-com-gatsby-to-nextjs), and that got me thinking - How much effort could it possibly be to use Next.js as the core of a SaaS?

I decided to do a quick test, and built a webpage to manually check if any URL is available, in a single afternoon (it's now part of the landing page at [OnlineOrNot.com](https://onlineornot.com/)). I was impressed by how seamlessly Next.js blended together React, server-side rendering, and API routes - compared to my regular approach of using Gatsby.js as a landing page, create-react-app as the SaaS app, and a ton of AWS Lambda functions to handle the backend.

## Tech Stack

Keeping in mind that I prefer to [choose boring technology](https://mcfunley.com/choose-boring-technology) - I could have used DynamoDB/MongoDB to keep running costs as low as possible, but I opted for a relational data model.

Anyway, here's the tech stack:

- Core app: Next.js, hosted on [Vercel](https://vercel.com/)
- UI/CSS framework: [Tailwind CSS](https://tailwindcss.com)
- Auth: [NextAuth.js](https://next-auth.js.org/) - a Next.js library
- Backend: GraphQL + Node.js, written in TypeScript, sitting on a [Next.js API route](https://nextjs.org/docs/api-routes/introduction)
- Database: Postgres
- Worker functions: Node.js/TypeScript, hosted on AWS Lambda
- Payment integration: [Stripe](https://stripe.com)
- Transactional Emails: [Mailgun](https://www.mailgun.com/) - I'd normally prefer to use [Postmark](https://postmarkapp.com/), but didn't want to blocked from shipping by having to wait around to get verified
- Newsletter Emails: [Convertkit](https://app.convertkit.com/referrals/l/4f6b4767-e189-42b0-b0eb-d171435c247d) (referral link)

## Approach

My work hours for my employer are 9am to 5pm, I tend to wake up at 7am, and go to bed around 11pm. If I gave 100% of my own time to this build, I would have had around 40 hours to work with.

In this particular week, I still either went to the gym, or ran each day, watched some Netflix, had dinner, and went out one night, so I had roughly 20 hours to work with.

## Building the SaaS

### First, a blog

To begin with, OnlineOrNot started its life as a blog. In particular, I cloned this example: [blog-starter-typescript](https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript) from the Next.js repo.

To get most of the blog functionality I wanted (sitemap, RSS feed, and code snippets in particular), I followed [these steps](https://maxrozen.com/walkthrough-migrating-maxrozen-com-gatsby-to-nextjs#first-steps-with-nextjs).

### Uptime Checker Test

With the blog running smoothly, I built an additional page to manually check uptime (the form you see at [OnlineOrNot.com](https://onlineornot.com))

To get technical with you, the form submits data to a page living under `status/[...url].tsx` in my Next.js pages folder. Once it gets a request, it sends off a request to an [API route](https://nextjs.org/docs/api-routes/introduction), which then sends the request to one of ten serverless functions deployed around the world (I had to use AWS Lambda for this part).

Seeing app-like functionality work so seamlessly on a blog is what inspired me to see how long it would take me to build a whole SaaS around the AWS Lambda function I wrote.

### On Auth

For auth I initially wanted to use Passport, and try out Max Stoiber's recently released [passport-magic-login](https://github.com/mxstbr/passport-magic-login), but I didn't enjoy the user experience of logging into an app with your email only, checking your email, clicking a link, then having to find the original tab to be able to use the app.

After a bit of searching, I came across [NextAuth.js](https://next-auth.js.org/), which boasted similar functionality (and heaps more Auth Providers supported), with a bit more polish (to be fair, it's been around longer). It only took me a couple of hours to have both email-only login, and Google Auth0 login working.

### Using Tailwind for UI

My typical approach to building UI rapidly would still be to use Bootstrap. In React, I opt for the [Reactstrap library](https://reactstrap.github.io/). It's never going to win me any design awards, but it helps me get the job done fast.

Since rewriting my own blog from Gatsby to Next.js, I've started writing my CSS using Tailwind utility classes. I find that whereas with Reactstrap I would use the default Bootstrap styles, then manually override each component, Tailwind has me thinking "How can I make this a component I can re-use?" more often, and it makes the code feel cleaner as a result.

To make my use of Tailwind CSS even faster, I paid for [Tailwind UI](https://tailwindui.com/), which has removed the "oh crap, where do I even start?" step for me when working on the frontend. It's kind of like paying for a designer to tell you "here's what screens like this tend to look like" - I highly recommend it.

### On SSR and Emotion

One pitfall I noticed with server-side rendering (SSR) and Emotion, is that some of the Tailwind classes use the `:first-child` selector, and Emotion's SSR support (a CSS in JS library) extracts its CSS to be the first sibling of your component.

As a result, your pages tend to flicker on first load. The fix is simple enough - in Next.js you need to extract the critical styles into the head of the page (I followed the instructions [here](https://github.com/ben-rogerson/twin.examples/tree/master/next-emotion#extract-styling-on-server-optional) since I used [twin.macro](https://github.com/ben-rogerson/twin.macro) to use Tailwind with Emotion).

### Forms!

I used to hate building forms in React until I came across [React Hook Form](https://react-hook-form.com/).

Importing the hook, passing `ref={register({ required: true })}` to a few of my inputs, and implementing a submit handler is all it takes to build awesome forms in minutes these days (though it helps that I've spent a couple of years building forms in React).

![OnlineOrNot Add Page Form](https://onlineornot.com/assets/building-saas-in-one-week-how-built-onlineornot/onlineornot-add-page.jpeg)

Once the user submits the form, they get to see their page being monitored:

![OnlineOrNot Your Pages List](https://onlineornot.com/assets/building-saas-in-one-week-how-built-onlineornot/onlineornot-your-pages.jpg)

### Latency troubles

Two days into building the SaaS with login/signup working, and it being possible to add pages to monitor, I decided to ship a v0.0.1 for friends to check out. I'm very glad I did, because I immediately noticed doing almost anything on the production build of the site took 3-6 seconds per click.

I eventually realised that the issue was caused by where I host my database. I typically host my Postgres database in AWS's ap-southeast-2 (Sydney, Australia) region, and Vercel hosts their functions somewhere on the US East Coast - probably in AWS's us-east-1 region.

So each request would hit their CDN, travel half-way across the world to the US, travel half-way across the world back to Sydney, Australia, and back to the US to finish the request.

Shutting down my Sydney database and spinning up a new one in AWS's us-east-1 region greatly reduced the latency issues.

### Docs

Before launching, I knew I needed documentation - even though initially I was just building OnlineOrNot as a test to see how much effort is involved in building a SaaS in 2021, as a developer, nothing pisses me off more than crap/incomplete/missing documentation.

I followed the instructions at [Docusaurus' homepage](https://v2.docusaurus.io/), spun up a quick site in Vercel, and mounted it onto the /docs path of OnlineOrNot - a pretty nifty [Vercel feature](https://vercel.com/docs/configuration#routes) that I loved from AWS CloudFront.

### Stripe integration

Integrating Stripe's Checkout requires adding products in the Stripe dashboard, and following the [docs](https://stripe.com/docs/payments/checkout) - there are also examples to follow.

Since I implemented my last Stripe integration, Stripe launched [Billing](https://stripe.com/en-au/billing) - greatly reducing the amount of effort required to let users upgrade, change, or cancel their subscriptions.

One of my employer's core values is "Don't f\*\*k the customer", and I _strongly_ identify with it, carrying it with me even when building side-projects. So I knew I had to have Stripe Billing to let my customers cancel at any time.

Rather than build the whole integration myself, I paid for [Divjoy](https://divjoy.com/?via=oon) (referral link), which conveniently includes a Stripe integration (with Checkout, Billing, and a webhook endpoint). While I only wanted the Billing portion of the codebase, it was money well-spent - it gave me ideas to refactor the integration I built by following the Stripe docs.

## Shipped!

With the Stripe integration built and tested, I deployed everything, and here we are - one week after I started toying around with a simple manual uptime checker in Next.js, I have a SaaS with login/sign-up, an app that tracks page uptime, and a way to pay for it (there's also a [free tier](https://onlineornot.com/docs/free-tier)).

## Where to next?

Glad you asked!

I keep a [changelog](https://onlineornot.com/changelog) and a [product roadmap](https://onlineornot.com/docs/roadmap) tracking what's on the radar, what's coming soon, and what's released - I intend to continue shipping features for OnlineOrNot, and continue running it as part of my side-business.

**Want to give OnlineOrNot a go?**

Get started on the free tier, with 10 reliable monitors, and email notifications - no credit card required.

[Get started for free](https://onlineornot.com/get-started)