DEV Community

Xuanming L.
Xuanming L.

Posted on

How to make boilerplate for React + Typescript + TailwindCSS + Auth + Vite

I have seen many posts that explains how to make React application using Typescript, or using TailwindCSS, or using Authenication, or using Vite.
But I realized that there is no post that explains all in one.
Today I am going to explain how we can build up a boilerplate that uses React, TailwindCSS, Authenication and Vite.
The full source code in on Github repository
Now let's go.

Prerequisites

  1. Node version ≥ 18.
  2. NPM version 8.

Vite requires Node.js version ≥ 18. and npm version 8. However, some templates require a higher Node.js version to work.

Create a Vite React application

Open the terminal and run the following command.

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Give a project name here. I am going to name it vite-react-boilerplate

Image description

Next select React using keyboard arrow key, then press Enter.

Image description

Then select Typescript or Typescript + SWC.

Image description

Finally install dependencies for the project.
Now navigate your project folder and then run following command.

cd vite-react-boilerplate
npm i
Enter fullscreen mode Exit fullscreen mode

Done.
Now you can test your project by running following command.

npm run dev
Enter fullscreen mode Exit fullscreen mode

Add TailwindCSS to your project

Now it's time to add TailwindCSS to your project
Run following command.

npm i -D tailwindcss postcss autoprefixer
Enter fullscreen mode Exit fullscreen mode

This command will install dependencies as dev-dependencies.
After installation, create TailwindCSS configuration by running following command.

npm tailwind init -p
Enter fullscreen mode Exit fullscreen mode

Then you will get 2 files: tailwind.config.js and postcss.config.js.
Now open tailwindcss.config.js file, and add following chanages.

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    // You will add this 2 lines.
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

And then add TailwindCSS directives to index.css file.

@tailwind base;
@tailwind components;
@tailwind utilities;

...
Enter fullscreen mode Exit fullscreen mode

Done.
Now you can use any TailwindCSS functionality.

Authenticate

Now it's time to implement authentication.
To do it, I will use the React Router DOM and create an Authentication Provider.
For the preparing, run following command in terminal.

npm i react-router-dom js-base64
npm i -D @types/node
Enter fullscreen mode Exit fullscreen mode

And then edit vite.config.ts to enable import path alias.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

First though, I will create Authenticate Provider.
Create a file under src/lib, then name it auth-util.tsx. Then input following code into it.

import React, {createContext, useContext, useState} from "react";

interface AuthContextProps {
  isAuthenticated: boolean;
  loginUser: () => void;
  logoutUser: () => void;
}

const AuthContext = createContext<AuthContextProps | undefined>(undefined);

export const AuthProvider = (
  {
    children,
  } : {
    children: React.ReactNode,
  }
) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const loginUser = () => setIsAuthenticated(true);
  const logoutUser = () => setIsAuthenticated(false);

  return (
    <AuthContext.Provider 
      value={{isAuthenticated, loginUser, logoutUser}}>
      {children}
    </AuthContext.Provider>
  )
}

export const useAuth = (): AuthContextProps => {
  const context = useContext(AuthContext);

  if (!context) {
    throw new Error("useAuth muse be used within an AuthProvider");
  }

  return context;
}
Enter fullscreen mode Exit fullscreen mode

Next, create a file under src/components, name it PrivateRoute.tsx. Then input following code into it.

import {Outlet, Navigate, type Path, useLocation} from "react-router-dom";
import {encode} from "js-base64";

import {useAuth} from "@/lib/auth-util";

export default function PrivateRoute(
  {
    loginUrl,
  } : {
    loginUrl: string | Partial<Path>
  }
) {
  const {isAuthenticated} = useAuth();
  const {pathname} = useLocation();

  return isAuthenticated ? <Outlet /> : <Navigate to={`${loginUrl}?redirect=${encode(pathname)}`} replace />;
}
Enter fullscreen mode Exit fullscreen mode

And then, create a file under src/pages/LoginPage.tsx, and input following code into it.

import {MouseEvent, useState} from "react";
import {useAuth} from "@/lib/auth-util";
import {useNavigate, useLocation} from "react-router-dom";
import {decode} from "js-base64";

const loginData = {
  email: "admin@example.com",
  password: "password",
};

export default function LoginPage() {
  const {loginUser} = useAuth();
  const navigate = useNavigate();
  const {search} = useLocation();

  const [email, setEmail] = useState(loginData.email);
  const [password, setPassword] = useState(loginData.password);

  const login = (e: MouseEvent) => {
    e.preventDefault();

    if (email === loginData.email && password === loginData.password) {
      loginUser();
      const queryParameters = new URLSearchParams(search);
      const redirect = queryParameters.get("redirect");
      navigate(redirect ? decode(redirect) : "/");
    }
  }

  return (
    <>
      <div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
        <div className="sm:mx-auto sm:w-full sm:max-w-sm">
          <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
            Sign in to your account
          </h2>
        </div>

        <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
          <form className="space-y-6" action="#" method="POST">
            <div>
              <div className="flex items-center justify-between">
                <label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
                  Email address
                </label>
              </div>
              <div className="mt-2">
                <input id="email" name="email" type="email"
                       autoComplete="email" required
                       className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                       value={email}
                       onChange={e => setEmail(e.target.value)}
                />
              </div>
            </div>

            <div>
              <div className="flex items-center justify-between">
                <label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
                  Password
                </label>
              </div>
              <div className="mt-2">
                <input id="password" name="password" type="password"
                       autoComplete="current-password" required
                       className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                       value={password}
                       onChange={e => setPassword(e.target.value)}
                />
              </div>
            </div>

            <div>
              <button
                type="submit"
                className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                onClick={login}
              >
                Sign in
              </button>
            </div>
          </form>
        </div>
      </div>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

And then add Homepage and AccountPage.

// src/pages/HomePage.tsx
export default function HomePage() {
  return (
    <>HomePage</>
  )
}
Enter fullscreen mode Exit fullscreen mode
// src/pages/AccountPage.tsx
export default function AccountPage() {
  return (
    <div>Account Page</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Now we have all pages to required for app, so let's create Navbar component.

// src/components/Navbar.tsx
import {Link, useNavigate} from "react-router-dom";
import {Disclosure} from "@headlessui/react";
import {ArrowLeftEndOnRectangleIcon, ArrowLeftStartOnRectangleIcon } from "@heroicons/react/24/outline";

import {useAuth} from "@/lib/auth-util";

const navigation = [
  { name: "Home", href: "/" },
  { name: "Account", href: "/account" },
];

export default function Example() {
  const {isAuthenticated, logoutUser} = useAuth();
  const navigate = useNavigate();

  return (
    <Disclosure as="nav" className="bg-gray-800">
      <div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
        <div className="relative flex h-16 items-center justify-between">
          <div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
            <div className="hidden sm:ml-6 sm:block">
              <div className="flex space-x-4">
                {navigation.map((item) => (
                  <Link
                    key={item.name}
                    to={item.href}
                    className="text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium"
                  >
                    {item.name}
                  </Link>
                ))}
              </div>
            </div>
          </div>
          <div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
            <button
              type="button"
              className="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" onClick={() => isAuthenticated ? logoutUser() : navigate("/login")} >
              <span className="absolute -inset-1.5" />
              <span className="sr-only">View notifications</span>
              {isAuthenticated && <ArrowLeftStartOnRectangleIcon className="h-6 w-6" aria-hidden="true" />}
              {!isAuthenticated && <ArrowLeftEndOnRectangleIcon className="h-6 w-6" aria-hidden="true" />}
            </button>
          </div>
        </div>
      </div>
    </Disclosure>
  )
}

Enter fullscreen mode Exit fullscreen mode

Finally, change App.tsx file as follows.

import {BrowserRouter as Router, Routes, Route} from "react-router-dom";

import LoginPage from "@/pages/LoginPage.tsx";
import HomePage from "@/pages/HomePage.tsx";
import AccountPage from "@/pages/AccountPage.tsx";

import Navbar from "@/components/Navbar";
import PrivateRoute from "@/components/PrivateRoute.tsx";
import {AuthProvider} from "@/lib/auth-util.tsx";

import "./App.css";

function App() {

  return (
    <AuthProvider>
      <Router>
        <Navbar />
        <Routes>
          <Route path="/login" element={<LoginPage />} />
          <Route path="/" element={<HomePage />} />
          <Route path="/" element={<PrivateRoute loginUrl={"/login"}/>} >
            <Route path="/account" element={<AccountPage />} />
          </Route>
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default App

Enter fullscreen mode Exit fullscreen mode

Done.

Now we have the boilerplate for React + Typescript + TailwindCSS + Auth + Vite

Top comments (0)