This guide will walk you through the process of creating a web application using Next.js, React, and Tailwind CSS. The application will include pages for the homepage, login, register, and dashboard, and will implement a persistent Redux store with types, actions, reducers, and sagas. Additionally, it will support both database and social login, feature light and dark themes, and include header and footer components. We will also set up private and public routes and provide commands for building, starting, and deploying the application on Vercel.
Prerequisites
- Node.js installed on your machine
- Basic knowledge of JavaScript and React
- Familiarity with Redux and Next.js
1. Setting Up the Next.js Project
- Create a new Next.js application:
npx create-next-app my-next-app
cd my-next-app
Use below command to create a new Next.js application with TypeScript:
npx create-next-app my-next-app --typescript
cd my-next-app
- Install Tailwind CSS: Follow the official Tailwind CSS installation guide for Next.js:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
- Configure Tailwind CSS: Update tailwind.config.js:
module.exports = {
content: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
- Add Tailwind to your CSS: In styles/globals.css, add the following:
@tailwind base;
@tailwind components;
@tailwind utilities;
2. Setting Up Redux
- Install Redux and related libraries:
npm install redux react-redux redux-saga
-
Create Redux Store: Create a folder named
store
and add the following files:- store/index.js:
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
-sagaMiddleware.run(rootSaga);
export default store;- store/reducers/index.js:
import { combineReducers } from 'redux';
import authReducer from './authReducer';
const rootReducer = combineReducers({
auth: authReducer,
});
export default rootReducer;- store/reducers/authReducer.js:
const initialState = {
user: null,
loading: false,
error: null,
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOGIN_REQUEST':
return { ...state, loading: true };
case 'LOGIN_SUCCESS':
return { ...state, loading: false, user: action.payload };
case 'LOGIN_FAILURE':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
export default authReducer;- store/sagas/index.js:
import { all } from 'redux-saga/effects';
import authSaga from './authSaga';
export default function\* rootSaga() {
yield all([
authSaga(),
]);
}- store/sagas/authSaga.js:
import { call, put, takeEvery } from 'redux-saga/effects';
import { loginService } from '../services/authService';
function\* login(action) {
try {
const user = yield call(loginService, action.payload);
yield put({ type: 'LOGIN_SUCCESS', payload: user });
} catch (error) {
yield put({ type: 'LOGIN_FAILURE', payload: error.message });
}
}
export default function\* authSaga() {
yield takeEvery('LOGIN_REQUEST', login);
} -
Create Services for MySQL: Create a folder named
services
and add the following file:- services/authService.js:
import axios from 'axios';
export const loginService = async (credentials) => {
const response = await axios.post('/api/login', credentials);
return response.data;
};
3. Creating Pages
- Homepage (
pages/index.js
):
import Head from 'next/head';
const Home = () => {
return (
<div>
<Head>
<title>Home</title>
</Head>
<h1 className="text-4xl">Welcome to My Next.js App</h1>
</div>
);
};
export default Home;
- Login Page (
pages/login.js
):
import { useDispatch } from 'react-redux';
const Login = () => {
const dispatch = useDispatch();
const handleLogin = (e) => {
e.preventDefault();
const credentials = {
username: e.target.username.value,
password: e.target.password.value,
};
dispatch({ type: 'LOGIN_REQUEST', payload: credentials });
};
return (
<form onSubmit={handleLogin}>
<input name="username" type="text" placeholder="Username" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Login</button>
</form>
);
};
export default Login;
- Register Page (
pages/register.js
):
const Register = () => {
return (
<form>
<input name="username" type="text" placeholder="Username" required />
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Register</button>
</form>
);
};
export default Register;
- Dashboard Page (
pages/dashboard.js
):
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
</div>
);
};
export default Dashboard;
4. Implementing Themes
-
Create a Theme Context: Create a folder named
context
and add the following file:- context/ThemeContext.js:
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
};
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;
};
export const useTheme = () => useContext(ThemeContext);
- Wrap the Application with ThemeProvider: In
pages/\_app.js
, wrap your application with theThemeProvider
:
import { ThemeProvider } from '../context/ThemeContext';
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;
5. Creating Header and Footer Components
- Header Component (
components/Header.js
):
import Link from 'next/link';
import { useTheme } from '../context/ThemeContext';
const Header = () => {
const { toggleTheme } = useTheme();
return (
<header>
<nav>
<Link href="/">Home</Link>
<Link href="/login">Login</Link>
<Link href="/register">Register</Link>
<Link href="/dashboard">Dashboard</Link>
<button onClick={toggleTheme}>Toggle Theme</button>
</nav>
</header>
);
};
export default Header;
- Footer Component (
components/Footer.js
):
const Footer = () => {
return (
<footer>
<p>© 2024 My Next.js App</p>
</footer>
);
};
export default Footer;
- Include Header and Footer in Pages: Update your pages to include the Header and Footer components:
import Header from '../components/Header';
import Footer from '../components/Footer';
const Home = () => {
return (
<div>
<Header />
<h1>Welcome to My Next.js App</h1>
<Footer />
</div>
);
};
export default Home;
6. Implementing Private and Public Routes
-
Create a Higher-Order Component for Route Protection: Create a folder named
hocs
and add the following file:- hocs/withAuth.js:
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import React from 'react';
const withAuth = (WrappedComponent) => {
const AuthenticatedComponent = (props) => {
const router = useRouter();
const user = useSelector((state) => state.auth.user);
// Redirect to login if user is not authenticated
React.useEffect(() => {
if (!user) {
router.push('/login');
}
}, [user, router]);
return user ? <WrappedComponent {...props} /> : null;
};
return AuthenticatedComponent;
};
export default withAuth;
-
Protect the Dashboard Page: Update your Dashboard page to use the withAuth HOC:
- pages/dashboard.js:
import withAuth from '../hocs/withAuth';
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
</div>
);
};
export default withAuth(Dashboard); -
Public Route Example: For pages like login and register, you can create a similar HOC to prevent logged-in users from accessing these pages:
- hocs/withPublic.js:
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import React from 'react';
const withPublic = (WrappedComponent) => {
const PublicComponent = (props) => {
const router = useRouter();
const user = useSelector((state) => state.auth.user);
// Redirect to dashboard if user is already authenticated
React.useEffect(() => {
if (user) {
router.push('/dashboard');
}
}, [user, router]);
return <WrappedComponent {...props} />;
};
return PublicComponent;
};
export default withPublic; -
Update Login and Register Pages: Use withPublic on the login and register pages:
- pages/login.js:
import { useDispatch } from 'react-redux';
import withPublic from '../hocs/withPublic';
const Login = () => {
const dispatch = useDispatch();
const handleLogin = (e) => {
e.preventDefault();
const credentials = {
username: e.target.username.value,
password: e.target.password.value,
};
dispatch({ type: 'LOGIN_REQUEST', payload: credentials });
};
return (
<form onSubmit={handleLogin}>
<input name="username" type="text" placeholder="Username" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Login</button>
</form>
);
};
export default withPublic(Login);- pages/register.js:
import withPublic from '../hocs/withPublic';
const Register = () => {
return (
<form>
<input name="username" type="text" placeholder="Username" required />
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Register</button>
</form>
);
};
export default withPublic(Register);
7. Implementing Light and Dark Themes
-
Add Theme Classes: Update your
ThemeProvider
to apply dark and light theme classes to the app.- context/ThemeContext.js:
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
};
return (
<div className={theme}>
<ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>
</div>
);
}; -
Add Tailwind CSS for Themes: In your
styles/globals.css
, include styles for dark mode:
/* Dark mode styles */
.dark {
@apply bg-gray-900 text-white;
}
- Update Components: Ensure your components utilize the theme classes accordingly.
8. Commands for Build, Start, and Deploy on Vercel
- Build Command: Add the following scripts to your
package.json
file:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"deploy": "vercel --prod"
}
-
Deploying to Vercel:
- First, install the Vercel CLI globally if you haven't already:
npm install -g vercel
- To deploy your application, run the following command in your project directory:
vercel
- Follow the prompts to link your project to a Vercel account.
To deploy to production, use:
npm run deploy
Conclusion
You have now set up a Next.js application using React and Tailwind CSS with a complete functionality including user authentication, light and dark themes, and routing. You can further extend this application by adding more features as needed. Happy coding!
Additional Resources
This guide serves as a comprehensive starting point. Feel free to customize and enhance your application as you see fit!