How to make boilerplate for React + Typescript + TailwindCSS + Auth + Vite
Tags: react, tailwindcss, authjs, vite
Published: 2024-06-11 08:48:58

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](https://github.com/isoftchamp/react-ts-auth-vite-boilerplate)** 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.
```terminal
npm create vite@latest
```

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sv8ek2htkwub7iw4qbf1.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/axvapn22c74vowfxjalc.png)

Then select **Typescript** or **Typescript + SWC**.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4r7fhddvpvj4mf3n7ped.png)

Finally install dependencies for the project.
Now navigate your project folder and then run following command.
```console
cd vite-react-boilerplate
npm i
```
Done.
Now you can test your project by running following command.
```console
npm run dev
```


## Add TailwindCSS to your project
Now it's time to add **TailwindCSS** to your project
Run following command.
```console
npm i -D tailwindcss postcss autoprefixer
```
This command will install dependencies as dev-dependencies.
After installation, create **TailwindCSS** configuration by running following command.
```console
npm tailwind init -p
```
Then you will get 2 files: **tailwind.config.js** and **postcss.config.js**.
Now open **tailwindcss.config.js** file, and add following chanages.
```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    // You will add this 2 lines.
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```
And then add **TailwindCSS** directives to **index.css** file.
```css
@tailwind base;
@tailwind components;
@tailwind utilities;

...
```
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.
```console
npm i react-router-dom js-base64
npm i -D @types/node
```
And then edit vite.config.ts to enable import path alias.
```typescript
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"),
    }
  }
})
```
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.
```tsx
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;
}
```
Next, create a file under src/components, name it PrivateRoute.tsx. Then input following code into it.
```tsx
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 />;
}
```
And then, create a file under src/pages/LoginPage.tsx, and input following code into it.
```tsx
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>
    </>
  )
}
```
And then add **Homepage** and **AccountPage**.
```tsx
// src/pages/HomePage.tsx
export default function HomePage() {
  return (
    <>HomePage</>
  )
}
```
```tsx
// src/pages/AccountPage.tsx
export default function AccountPage() {
  return (
    <div>Account Page</div>
  )
}
```
Now we have all pages to required for app, so let's create **Navbar** component.
```tsx
// 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>
  )
}

```
Finally, change **App.tsx** file as follows.
```tsx
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

```
Done.

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