DEV Community

Cover image for Refactoring landing page with React, NextJS & TailwindCSS
Dimitris Kapanidis
Dimitris Kapanidis

Posted on • Originally published at codingholygrail.com

Refactoring landing page with React, NextJS & TailwindCSS

In this blog post I describe the refactoring process that took place in our product landing page Kubernetic, in order to get a more clean UI. The whole process took 5 days and was a complete rewrite of the landing page, including the trial signup form and stripe integration for payments.

The main reason of the refactor was to test out TailwindCSS framework and their utility-first design. I'm not sure if they coined the term but that was the first time I encountered it and wanted to try it out and see the benefits / inconveniences in a real-world use case. The landing page is a small website that desperately needed a lift-up so it fitted the description. You can check the final code of the landing page on GitHub.

On the following sections I will describe each decision and give a how-to guide to reproduce the setup starting from scratch to deploying in production.

NextJS Starter

Since we do a fresh start with the landing page, it was time to use NextJS instead of Create React App (CRA).

In React website there is a recommended toolchains section where they describe CRA as best fit if you're learning React or building a single-page app, while NextJS is best fit for server-rendered website with NodeJS.

Probably this landing page would fit in the CRA definition since we don't use NodeJS for server-side, but still I found myself more comfortable with NextJS guiding me with an opinionated way to build stuff (e.g. pages structure), and the integration during deployment to production or with TailWindCSS which we'll discuss further below.

For NextJS there is a quick starter template you can use to bootstrap your repo:

$ npx create-next-app nextjs-blog --use-npm --example \
https://github.com/vercel/next-learn-starter/tree/master/learn-starter
Enter fullscreen mode Exit fullscreen mode

Once you have the repository you can run the app using npm run dev and open the http://localhost:3000.

Alt Text

NextJS + TailwindCSS Starter

TailwindCSS is essentially a PostCSS plugin, so in order to integrate it you need to install PostCSS first, a tool for transforming CSS with JavaScript. There is a great guide for integrating NextJS with TailwindCSS here which I definitely would recommend to read and do the exercise yourself so that you can better understand the concepts and mechanisms behind the curtain. For faster bootstrapping though you can use the following starter instead that already comes prepared:

$ npx create-next-app nextjs-blog --use-npm --example \
https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss
Enter fullscreen mode Exit fullscreen mode

Now run npm run dev and open http://localhost:3000 on your browser:

next+tailwind

Enable TypeScript

There are always strong opinions between javascript vs typescript, choose the one you're more comfortable with, I personally prefer typescript from javascript because it is a strongly typed superset, which can provide a compilation-time validation of the types, and the IDE (my current favourite is Visual Studio Code) is providing helpful insight when I'm coding.

In case you want to enable typescript, NextJS provide a nice onboarding process. First, create an empty tsconfig.json file in the root of your project:

touch tsconfig.json
Enter fullscreen mode Exit fullscreen mode

Once created run npm run dev and follow the instructions to install the dependencies:

npm run dev

# You'll see instructions like these:
#
# Please install typescript, @types/react, and @types/node by running:
#
#         yarn add --dev typescript @types/react @types/node
#
# ...
Enter fullscreen mode Exit fullscreen mode

Once the dependencies are installed, on the next run the following configuration files are auto-generated for you tsconfig.json, next-env.d.ts. Now you can start converting .js files to .tsx use typescript in the project.

Pages Structure

One of the things I really liked on NextJS is the predefined Pages layout.

In a nutshell if you create for example pages/about.tsx that exports a React component, it will be accessible at /about.

It also supports pages with dynamic routes. For example, if you create a file called pages/posts/[id].tsx, then it will be accessible at posts/1, posts/2, etc.

In our landing repo the pages structure is the following:

  • index.tsx - The landing page.
  • enterprise/trial.tsx - Signup form for trial of Kubernetic Enterprise, sent to Netlify Forms.
  • payment/checkout.tsx - Form for payment of Kubernetic Desktop, sent to Stripe.
  • payment/success.tsx - The redirect page from successful Stripe payments.

tsconfig.json

I'm not very fan of fiddling with the tsconfig.json as I like to keep it as lean as possible, but there is one change I like, adding baseURL and the corresponding paths (tsconfig reference):

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@components/*": ["components/*"],
      "@utils/*": ["utils/*"],
      "@styles/*": ["styles/*"]
    },
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Now your imports are relative to the root directory instead of being relative to current directory. So the index.tsx file can now be updated to the following:

# OLD import without defined baseUrl:
# import Nav from '../components/nav'

# NEW import with defined baseUrl:
import Nav from '@components/nav'


export default function IndexPage() {
  return (
    <div>
      <Nav />
      <div className="py-20">
        <h1 className="text-5xl text-center text-accent-1">
          Next.js + Tailwind CSS
        </h1>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

If you get tired of imports always looking like "../" or "./". Or needing to change as you move files, this is a great way to fix that.

Call-To-Action Button (CTA)

The CTA button has a principal download link with a nice-looking dropdown menu that displays option for different OS. I've added a slight shadow on hover and a transition movement of 1-px in 0.3secs which gives the illusion of button popup.

cta button

All this is very easy to do with TailWindCSS and it gave me a pixel perfect freedom that I didn't feel before. This is exactly what I was looking for with the utility-first design without entering the here-be-dragons CSS world. Arguably, you could still say it's CSS, but I'd say it's a bit more abstract, and it sits somewhere between CSS and pre-cooked UI frameworks such as Bootstrap or Material UI.

The actual CTAButton can be found here. First create the SVG icons of the CTA button as a separate component components/Icons.tsx:

export function AppleIcon() {
  return (<svg className="fill-current place-self-center align-middle w-4 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 315" version="1.1" >
    <g>
      <path d="M213.803394,167.030943 C214.2452,214.609646 255.542482,230.442639 256,230.644727 C255.650812,231.761357 249.401383,253.208293 234.24263,275.361446 C221.138555,294.513969 207.538253,313.596333 186.113759,313.991545 C165.062051,314.379442 158.292752,301.507828 134.22469,301.507828 C110.163898,301.507828 102.642899,313.596301 82.7151126,314.379442 C62.0350407,315.16201 46.2873831,293.668525 33.0744079,274.586162 C6.07529317,235.552544 -14.5576169,164.286328 13.147166,116.18047 C26.9103111,92.2909053 51.5060917,77.1630356 78.2026125,76.7751096 C98.5099145,76.3877456 117.677594,90.4371851 130.091705,90.4371851 C142.497945,90.4371851 165.790755,73.5415029 190.277627,76.0228474 C200.528668,76.4495055 229.303509,80.1636878 247.780625,107.209389 C246.291825,108.132333 213.44635,127.253405 213.803394,167.030988 M174.239142,50.1987033 C185.218331,36.9088319 192.607958,18.4081019 190.591988,0 C174.766312,0.636050225 155.629514,10.5457909 144.278109,23.8283506 C134.10507,35.5906758 125.195775,54.4170275 127.599657,72.4607932 C145.239231,73.8255433 163.259413,63.4970262 174.239142,50.1987249"></path>
    </g>
  </svg>
  )
}

export function DropdownIcon() {
  return (<svg className="fill-current -mr-1 -ml-1 h-5 w-5 rounded-md" viewBox="0 0 20 20">
    <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
  </svg>
  )
}

export function WinIcon() {
  return (
    <svg className="fill-current w-4 mr-2" xmlns="http://www.w3.org/2000/svg" version="1.1"
      viewBox="-2.61977004 -2.61977004 92.56520808 92.83416708">
      <path
        d="M 0,12.40183 35.68737,7.5416 35.70297,41.96435 0.03321,42.16748 z m 35.67037,33.52906 0.0277,34.45332 -35.66989,-4.9041 -0.002,-29.77972 z M 39.99644,6.90595 87.31462,0 l 0,41.527 -47.31818,0.37565 z M 87.32567,46.25471 87.31457,87.59463 39.9964,80.91625 39.9301,46.17767 z" />
    </svg>
  )
}

export function LinuxIcon() {
  return (
    <svg className="fill-current w-4 mr-2" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 266 312">
      <g transform="translate(-3.3359375,285.2793)">
        <path d="M132-206c0,1-1,1-1,1h-1c-1,0-1-1-2-2,0,0-1-1-1-2s0-1,1-1l2,1c1,1,2,2,2,3m-18-10c0-5-2-8-5-8,0,0,0,1-1,1v2h3c0,2,1,3,1,5h2m35-5c2,0,3,2,4,5h2c-1-1-1-2-1-3s0-2-1-3-2-2-3-2c0,0-1,1-2,1,0,1,1,1,1,2m-30,16c-1,0-1,0-1-1s0-2,1-3c2,0,3-1,3-1,1,0,1,1,1,1,0,1-1,2-3,4h-1m-11-1c-4-2-5-5-5-10,0-3,0-5,2-7,1-2,3-3,5-3s3,1,5,3c1,3,2,6,2,9v1,1h1v-1c1,0,1-2,1-6,0-3,0-6-2-9s-4-5-8-5c-3,0-6,2-7,5-2,4-2.4,7-2.4,12,0,4,1.4,8,5.4,12,1-1,2-1,3-2m125,141c1,0,1-0.4,1-1.3,0-2.2-1-4.8-4-7.7-3-3-8-4.9-14-5.7-1-0.1-2-0.1-2-0.1-1-0.2-1-0.2-2-0.2-1-0.1-3-0.3-4-0.5,3-9.3,4-17.5,4-24.7,0-10-2-17-6-23s-8-9-13-10c-1,1-1,1-1,2,5,2,10,6,13,12,3,7,4,13,4,20,0,5.6-1,13.9-5,24.5-4,1.6-8,5.3-11,11.1,0,0.9,0,1.4,1,1.4,0,0,1-0.9,2-2.6,2-1.7,3-3.4,5-5.1,3-1.7,5-2.6,8-2.6,5,0,10,0.7,13,2.1,4,1.3,6,2.7,7,4.3,1,1.5,2,2.9,3,4.2,0,1.3,1,1.9,1,1.9m-92-145c-1-1-1-3-1-5,0-4,0-6,2-9,2-2,4-3,6-3,3,0,5,2,7,4,1,3,2,5,2,8,0,5-2,8-6,9,0,0,1,1,2,1,2,0,3,1,5,2,1-6,2-10,2-15,0-6-1-10-3-13-3-3-6-4-10-4-3,0-6,1-9,3-2,3-3,5-3,8,0,5,1,9,3,13,1,0,2,1,3,1m12,16c-13,9-23,13-31,13-7,0-14-3-20-8,1,2,2,4,3,5l6,6c4,4,9,6,14,6,7,0,15-4,25-11l9-6c2-2,4-4,4-7,0-1,0-2-1-2-1-2-6-5-16-8-9-4-16-6-20-6-3,0-8,2-15,6-6,4-10,8-10,12,0,0,1,1,2,3,6,5,12,8,18,8,8,0,18-4,31-14v2c1,0,1,1,1,1m23,202c4,7.52,11,11.3,19,11.3,2,0,4-0.3,6-0.9,2-0.4,4-1.1,5-1.9,1-0.7,2-1.4,3-2.2,2-0.7,2-1.2,3-1.7l17-14.7c4-3.19,8-5.98,13-8.4,4-2.4,8-4,10-4.9,3-0.8,5-2,7-3.6,1-1.5,2-3.4,2-5.8,0-2.9-2-5.1-4-6.7s-4-2.7-6-3.4-4-2.3-7-5c-2-2.6-4-6.2-5-10.9l-1-5.8c-1-2.7-1-4.7-2-5.8,0-0.3,0-0.4-1-0.4s-3,0.9-4,2.6c-2,1.7-4,3.6-6,5.6-1,2-4,3.8-6,5.5-3,1.7-6,2.6-8,2.6-8,0-12-2.2-15-6.5-2-3.2-3-6.9-4-11.1-2-1.7-3-2.6-5-2.6-5,0-7,5.2-7,15.7v3.3,11.6,8.9,4.3,3c0,0.9-1,2.9-1,6-1,3.1-1,6.62-1,10.6l-2,11.1v0.17m-145-5.29c9.3,1.36,20,4.27,32.1,8.71,12.1,4.4,19.5,6.7,22.2,6.7,7,0,12.8-3.1,17.6-9.09,1-1.94,1-4.22,1-6.84,0-9.45-5.7-21.4-17.1-35.9l-6.8-9.1c-1.4-1.9-3.1-4.8-5.3-8.7-2.1-3.9-4-6.9-5.5-9-1.3-2.3-3.4-4.6-6.1-6.9-2.6-2.3-5.6-3.8-8.9-4.6-4.2,0.8-7.1,2.2-8.5,4.1s-2.2,4-2.4,6.2c-0.3,2.1-0.9,3.5-1.9,4.2-1,0.6-2.7,1.1-5,1.6-0.5,0-1.4,0-2.7,0.1h-2.7c-5.3,0-8.9,0.6-10.8,1.6-2.5,2.9-3.8,6.2-3.8,9.7,0,1.6,0.4,4.3,1.2,8.1,0.8,3.7,1.2,6.7,1.2,8.8,0,4.1-1.2,8.2-3.7,12.3-2.5,4.3-3.8,7.5-3.8,9.78,1,3.88,7.6,6.61,19.7,8.21m33.3-90.9c0-6.9,1.8-14.5,5.5-23.5,3.6-9,7.2-15,10.7-19-0.2-1-0.7-1-1.5-1l-1-1c-2.9,3-6.4,10-10.6,20-4.2,9-6.4,17.3-6.4,23.4,0,4.5,1.1,8.4,3.1,11.8,2.2,3.3,7.5,8.1,15.9,14.2l10.6,6.9c11.3,9.8,17.3,16.6,17.3,20.6,0,2.1-1,4.2-4,6.5-2,2.4-4.7,3.6-7,3.6-0.2,0-0.3,0.2-0.3,0.7,0,0.1,1,2.1,3.1,6,4.2,5.7,13.2,8.5,25.2,8.5,22,0,39-9,52-27,0-5,0-8.1-1-9.4v-3.7c0-6.5,1-11.4,3-14.6s4-4.7,7-4.7c2,0,4,0.7,6,2.2,1-7.7,1-14.4,1-20.4,0-9.1,0-16.6-2-23.6-1-6-3-11-5-15-2-3-4-6-6-9s-3-6-5-9c-1-4-2-7-2-12-3-5-5-10-8-15-2-5-4-10-6-14l-9,7c-10,7-18,10-25,10-6,0-11-1-14-5l-6-5c0,3-1,7-3,11l-6.3,12c-2.8,7-4.3,11-4.6,14-0.4,2-0.7,4-0.9,4l-7.5,15c-8.1,15-12.2,28.9-12.2,40.4,0,2.3,0.2,4.7,0.6,7.1-4.5-3.1-6.7-7.4-6.7-13m71.6,94.6c-13,0-23,1.76-30,5.25v-0.3c-5,6-10.6,9.1-18.4,9.1-4.9,0-12.6-1.9-23-5.7-10.5-3.6-19.8-6.36-27.9-8.18-0.8-0.23-2.6-0.57-5.5-1.03-2.8-0.45-5.4-0.91-7.7-1.37-2.1-0.45-4.5-1.13-7.1-2.05-2.5-0.79-4.5-1.82-6-3.07-1.38-1.26-2.06-2.68-2.06-4.27,0-1.6,0.34-3.31,1.02-5.13,0.64-1.1,1.34-2.2,2.04-3.2,0.7-1.1,1.3-2.1,1.7-3.1,0.6-0.9,1-1.8,1.4-2.8,0.4-0.9,0.8-1.8,1-2.9,0.2-1,0.4-2,0.4-3s-0.4-4-1.2-9.3c-0.8-5.2-1.2-8.5-1.2-9.9,0-4.4,1-7.9,3.2-10.4s4.3-3.8,6.5-3.8h11.5c0.9,0,2.3-0.5,4.4-1.7,0.7-1.6,1.3-2.9,1.7-4.1,0.5-1.2,0.7-2.1,0.9-2.5,0.2-0.6,0.4-1.2,0.6-1.7,0.4-0.7,0.9-1.5,1.6-2.3-0.8-1-1.2-2.3-1.2-3.9,0-1.1,0-2.1,0.2-2.7,0-3.6,1.7-8.7,5.3-15.4l3.5-6.3c2.9-5.4,5.1-9.4,6.7-13.4,1.7-4,3.5-10,5.5-18,1.6-7,5.4-14,11.4-21l7.5-9c5.2-6,8.6-11,10.5-15s2.9-9,2.9-13c0-2-0.5-8-1.6-18-1-10-1.5-20-1.5-29,0-7,0.6-12,1.9-17s3.6-10,7-14c3-4,7-8,13-10s13-3,21-3c3,0,6,0,9,1,3,0,7,1,12,3,4,2,8,4,11,7,4,3,7,8,10,13,2,6,4,12,5,20,1,5,1,10,2,17,0,6,1,10,1,13,1,3,1,7,2,12,1,4,2,8,4,11,2,4,4,8,7,12,3,5,7,10,11,16,9,10,16,21,20,32,5,10,8,23,8,36.9,0,6.9-1,13.6-3,20.1,2,0,3,0.8,4,2.2s2,4.4,3,9.1l1,7.4c1,2.2,2,4.3,5,6.1,2,1.8,4,3.3,7,4.5,2,1,5,2.4,7,4.2,2,2,3,4.1,3,6.3,0,3.4-1,5.9-3,7.7-2,2-4,3.4-7,4.3-2,1-6,3-12,5.82-5,2.96-10,6.55-15,10.8l-10,8.51c-4,3.9-8,6.7-11,8.4-3,1.8-7,2.7-11,2.7l-7-0.8c-8-2.1-13-6.1-16-12.2-16-1.94-29-2.9-37-2.9" />
      </g>
    </svg >
  )
}
Enter fullscreen mode Exit fullscreen mode

Once the Icons are created you can create the CTA button components/CTAButton.tsx:

import Link from 'next/link'
import { useState } from "react"
import { AppleIcon, DropdownIcon, LinuxIcon, WinIcon } from "./Icons"

export default function CTAButton() {
  const [isOpen, updateIsOpen] = useState(false)
  return (
    <>
      <div className="inline-flex">
        <div className="relative">
          <div className="btn-popup inline-flex w-56 divide-x divide-green-600 hover:shadow-lg">
            <Link href="https://www.kubernetic.com/">
              <button className="btn btn-green inline-flex w-48 rounded-l  px-3 py-3 pl-4">
                <AppleIcon />
                <span>Download for Mac</span>
              </button>
            </Link>
            <button aria-label="choose-os" className="btn btn-green inline-flex transition rounded-r ease-in-out duration-150 px-3 py-3"
              onClick={() => updateIsOpen(!isOpen)}>
              <DropdownIcon />
            </button>
          </div>
          {isOpen && <DropdownMenu />}
        </div>
      </div>
    </>
  )
}


const DropdownMenu = () => (
  <div className="absolute">
    <ul className="w-56 ml-1 p-2 mt-2 text-gray-600 bg-white border border-gray-100 rounded-lg shadow-md min-w-max-content right-0" aria-label="submenu">
      <DropdownMenuItem icon={<WinIcon />} text="Download for Windows" to="https://www.kubernetic.com/" />
      <DropdownMenuItem icon={<LinuxIcon />} text="Download for Linux" to="https://www.kubernetic.com/" />
    </ul>
  </div>
)

type DropdownMenuProps = { icon: any, text: string, to: string }
function DropdownMenuItem({ icon, text, to }: DropdownMenuProps) {
  return (
    <Link href={to}>
      <li>
        <a className="inline-flex items-center cursor-pointer w-full px-2 py-2 text-sm font-medium transition-colors duration-150 rounded-md hover:bg-gray-200 hover:text-gray-800" type="button">
          {icon}
          <span>{text}</span>
        </a>
      </li>
    </Link>
  )
}
Enter fullscreen mode Exit fullscreen mode

Lastly, in order to be able to re-use the styling of the buttons elsewhere I've defined it in the index.css instead of inlining it on each component:

@tailwind base;

/* Write your own custom base styles here */

/* Start purging... */
@tailwind components;
/* Stop purging. */

/* Write your own custom component styles here */
.btn-blue {
  @apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
}

/* Start purging... */
@tailwind utilities;
/* Stop purging. */

/* Your own custom utilities */

/* Button */
.btn {
  @apply whitespace-no-wrap text-base font-medium items-center justify-center cursor-pointer;
}
.btn:focus {
  @apply outline-none shadow-outline;
}
.btn:hover {
  @apply shadow-lg;
}

/* Button Popup */
.btn-popup {
  @apply transition duration-300 ease-in-out transform;
}
.btn-popup:hover {
  @apply -translate-y-px;
}

/* Button Green */
.btn-green {
  @apply text-white bg-green-500;
}
.btn-green:focus {
  @apply border-green-700;
}
.btn-green:hover {
  @apply text-white bg-green-400;
}
.btn-green:active {
  @apply bg-green-700;
}
Enter fullscreen mode Exit fullscreen mode

Update your pages/ndex.tsx to include the CTAButton:

import CTAButton from '@components/CTAButton'
import Nav from '@components/nav'

export default function IndexPage() {
  return (
    <div>
      <Nav />
      <div className="py-20">
        <h1 className="text-center">
          <CTAButton/>
        </h1>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

You should now be able to see your own CTA Button:

new-cta

Deploy with Netlify

Previously our landing page was deployed on Google Storage Bucket which was served by Google Cloud CDN. The deployment was done manually, now we use Netlify and can't look back anymore. Here are some features that we currently use:

  • Pushes on master repository are automatically published on website.
  • Automatic HTTPS certificates with Let's Encrypt.
  • Pushes on develop branch are automatically published on dedicated URL.
  • Other branches and GitHub Pull Requests can also get dedicated URLs.

Netlify supports NextJS out-of-the-box so not much was necessary for the setup. Everything can be version-controlled under the netlify.toml file (Personally I'm not very fan of the TOML format, and prefer YAML although I know a lot of people don't like it much, but that's just a personal preference), if you have environment variables that are sensitive you can configure them on their UI instead. In our case everything is publishable (yes the Stripe keys are the public ones - don't be sneaky):

[build]
  command = "npm run build && npm run export"
  publish = "out"

[[plugins]]
package = "netlify-plugin-cache-nextjs"

[context.production.environment]
  NEXT_PUBLIC_LICENSESERVER_URL = ...
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = ...

[context.staging.environment]
  NEXT_PUBLIC_LICENSESERVER_URL = ...
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = ...

[context.branch-deploy.environment]
  NEXT_PUBLIC_LICENSESERVER_URL = ...
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = ...
Enter fullscreen mode Exit fullscreen mode
  • The build section defines the build command and output directory.
  • The plugins we've setup a cache for NetxtJS to make builds faster.
  • The environment sections define different variables that are injected during build depending the branch. While on master branch we use the production variables, on develop branch we test Stripe integration using their test environment. The same variables are also setup in the env.[local|production|development|test] files so that we use them outside of Netlify (e.g. a local running instance). env.local is not added in the version control so that it can be configured locally by each developer. The variables need to start with prefix NEXT_PUBLIC_ so that they are accessible from the browser itself (NextJS doc).

Of course deployment can also be done with Vercel, the authors of NextJS, there was no particular reason why I chose Netlify instead of Vercel, make sure to check out both before deciding, I'm personally happy with Netlify so I don't have a reason for now to switch.

Optimisations (Lighthouse)

Once deployed I took a bit of time to make sure the site is optimised properly for web, using lighthouse. Once I finished the optimisations here the result was pretty good:

lighthouse

One of the things I noticed there was the images I used on the landing page could be compressed better by serving them in next-gen formats. I then used cwebp CLI for static PNG and ezgif.com for animated PNGs.

Conclusion

NextJS is definitely worth to check it out if you're into React, even if you build a simple landing page it provides a great opinionated way to organize pages and components.

The TailwindCSS utility-first design is really cool, I can now do one-of designs on specific components without the need to name each element class on CSS. Although this should be used with caution in bigger projects, to avoid duplication of designs, thankfully custom components can be easily created and re-used as shown in the CSS file.

Lastly, with the image optimisations I gained 1 sec in First-Contentful-Paint time which can improve sales.

Top comments (4)

Collapse
 
iamschulz profile image
Daniel Schulz

Can you elaborate on why you used React for a simple landing page? As far as I can see, the whole page can be built with next to no JS at all.

Collapse
 
dkapanidis profile image
Dimitris Kapanidis

Certainly, I'm not advocating to use this for any type of landing page, especially if you're specialising on landing pages, starting out or you haven't worked with React yet.

Actually the previous version was exactly that (HTML + JS), but for my case it made sense for the following reasons:

  • I already know React since Kubernetic desktop client is react+electron based.
    Building landing pages is not part of my day job, it is more important to have unified tooling, so reusing React to the landing page is a plus, not an obstacle.

  • The website is not an SPA, it currently has 4 pages, so creating components to reuse (e.g. Header + Footer) makes is more manageable, and React helps with that, surely there are other templating mechanisms, but then we go back to reason 1.

  • I didn't explain in detail the Stripe payment, but one of the pages goes to Stripe, I needed a dev environment to test payments before production, this setup provided a dev environment on Netlify to test it with before pushing to production.

The main reason that triggered the refactor was the last one, I needed a way to test the payments with confidence before pushing to production, where money is involved.

The form is calculating if TAX should be added. I live in Spain - so EU rules apply - I check the user country and the VAT ID (EU business TAX ID number), this is done twice (frontend and backend for security) so I needed a way to test all scenarios before pushing to production.

Collapse
 
mrsid profile image
MrSid

Yeah, I agree with you and I think it's not necessary to use Reactjs for simple webpage like this. What do you think?

Collapse
 
victoredier profile image
Victor Edier

I think is a valid use for academic reasons, but I think is an overkill for a landing page, maybe there are more reasons IDK