How to make boilerplate for React + Typescript + TailwindCSS + Auth + Vite
Xuanming L.
Posted on June 11, 2024
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
- Node version ≥ 18.
- 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
Give a project name here. I am going to name it vite-react-boilerplate
Next select React using keyboard arrow key, then press Enter.
Then select Typescript or Typescript + SWC.
Finally install dependencies for the project.
Now navigate your project folder and then run following command.
cd vite-react-boilerplate
npm i
Done.
Now you can test your project by running following command.
npm run dev
Add TailwindCSS to your project
Now it's time to add TailwindCSS to your project
Run following command.
npm i -D tailwindcss postcss autoprefixer
This command will install dependencies as dev-dependencies.
After installation, create TailwindCSS configuration by running following command.
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.
/** @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.
@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.
npm i react-router-dom js-base64
npm i -D @types/node
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"),
}
}
})
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;
}
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 />;
}
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>
</>
)
}
And then add Homepage and AccountPage.
// src/pages/HomePage.tsx
export default function HomePage() {
return (
<>HomePage</>
)
}
// 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.
// 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.
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
Posted on June 11, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.