In the past two weeks, I have embarked on an intensive development journey, creating a sophisticated dashboard application using the Next.js framework along with Typescript, React, and various other technologies. This article delves into the specific tasks I tackled, the technologies I employed, and the insights I gained.
here is a look at the dashboard app
setting up the project
i used pnpm package manager as it is fast than npm or yarn i installed it globally bz running
npm install -g pnpm
creating a nextjs app i used this command :
npx create-next-app@latest nextjs-dashboard --example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example" --use-pnpm
This command uses create-next-app, a Command Line Interface (CLI) tool that sets up a Next.js application. In the command above, it also using the --example flag with the starter example for this project
1. Global Styles and Tailwind CSS
One of the initial steps in my project was to set up global styles using Tailwind CSS. Tailwind CSS is a utility-first CSS framework that facilitates rapid UI development by allowing developers to apply pre-defined classes directly in the markup. I integrated Tailwind CSS by configuring the tailwind.config.js file and incorporating the necessary CSS imports into the global stylesheet. This setup streamlined the design process and ensured consistent styling across the application.
Adding Global Styles
To apply global styles across the application, i used a global CSS file. In a Next.js project, i import this CSS file in the root layout.
// /app/layout.tsx
import '@/app/ui/global.css';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
This approach ensures that the styles defined in global.css are applied throughout your app.
and in /* /app/ui/global.css */
`/* /app/ui/global.css */`
@tailwind base;
@tailwind components;
@tailwind utilities;
These directives set up Tailwind's base styles, components, and utility classes, providing a foundation for the design
2. CSS Modules
For more component-specific styling, I employed CSS Modules. CSS Modules offer a scoped approach to CSS, ensuring that styles are applied only to the component they are defined in. This modularity prevents style conflicts and enhances maintainability. I utilized CSS Modules by creating .module.css files and importing them into React components, thereby achieving encapsulated and reusable styles.
Conditionally Adding Class Names with clsx
For dynamic styling, i used the clsx utility package. This package helps conditionally apply class names based on certain conditions.
for instance you will first need to install clsx
npm install clsx
then use in the component
import clsx from 'clsx';
export default function Button({ isActive }: { isActive: boolean }) {
return (
<button className={clsx('p-2 rounded', { 'bg-blue-500': isActive, 'bg-gray-500': !isActive })}>
Click me
</button>
);
}
In this example, the button's background color changes based on the isActive prop.
3. Setting Up and Seeding a PostgreSQL Database for Your Next.js Project
To effectively manage data in your Next.js application, setting up and seeding a PostgreSQL database is essential. This guide will walk you through the process of configuring a PostgreSQL database using Vercel's PostgreSQL service and seeding it with initial data. By the end of this guide, you'll have a fully functional database ready for your Next.js dashboard application.
Table of Contents :
- Create a GitHub Repository
- Create a Vercel Account and Deploy Your Project
- Create and Link Your PostgreSQL Database
- Seed Your Database
- Exploring and Querying Your Database -Troubleshooting
**
Create a GitHub Repository
**
If you haven’t set up a repository yet, follow this GitHub guide.
to get started. For a simplified workflow, consider using the GitHub Desktop App.
Note: You can use other Git providers like GitLab or Bitbucket if preferred.
**
Create a Vercel Account and Deploy Your Project
**
** Sign Up for Vercel:**
Visit vercel.com/signup to create an account.
Choose the free "Hobby" plan.
Select "Continue with GitHub" to connect your GitHub and Vercel accounts.
** Import and Deploy Your Project:**
After signing up, you'll be redirected to the Vercel dashboard.
Click "Import Project" and select your GitHub repository.
Name your project and click "Deploy."
boooom!!! there you go the project is now deployed. Vercel will automatically redeploy the application with each push to the main branch and provide instant previews for pull requests.
Create and Link Your PostgreSQL Database
Access Your Vercel Dashboard:
Navigate to your project’s dashboard on Vercel.
Click "Continue to Dashboard" and select the "Storage" tab.Create a New Database:
Click "Connect Store" → "Create New" → "Postgres" → "Continue."
Accept the terms, name your database, and set the region to Washington D.C (iad1), which is the default for new Vercel projects.-
Copy Database Secrets:
- Go to the .env.local tab in Vercel, click "Show secret," and copy the snippet.
- Rename the .env.example file in your project to .env and paste the copied contents from Vercel.
- Go to the .env.local tab in Vercel, click "Show secret," and copy the snippet.
Important: Ensure .env is listed in your .gitignore file to prevent exposing your database secrets in version control.
-
Install the Vercel Postgres SDK:
Run the following command to install the Vercel Postgres SDK:
pnpm i @vercel/postgres
Seed Your Database
Seeding your database involves populating it with initial data to test your application.
-
Prepare the Seed Script:
- Inside the
/app
directory, locate and uncomment theseed
folder. This folder containsroute.ts
, a Next.js Route Handler used to seed your database.
- Inside the
Run the Seed Script:
Ensure your local development server is running with:
pnpm run dev
after that you can Navigate to http://localhost:3000/seed
in your browser. You should see a message "Database seeded successfully."
**
4. Adding Authentication to Your Next.js Dashboard Application
I integrated authentication functionalities, including login and logout features. This process involved updating the login form, implementing secure authentication mechanisms, and ensuring proper session management.
here i will take you through :
- Setting Up NextAuth.js
- Protecting Routes with Middleware
- Implementing Password Hashing
- Adding Authentication Providers
- Updating the Login Form
- Adding Logout Functionality
- Testing Your Implementation
Setting Up NextAuth.js
To add authentication to the application,i used NextAuth.js. This library simplifies session management, sign-ins, and sign-outs.
- Install NextAuth.js
Run the following command to install the beta version compatible with Next.js 14:
pnpm i next-auth@beta
2.Generate a Secret Key
Generate a secret key for encrypting cookies:
openssl rand -base64 32
Add this key to your .env file:
AUTH_SECRET=your-secret-key
- Configure NextAuth.js
Create an auth.config.ts file in the root of your project:
// auth.config.ts
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
pages: {
signIn: '/login',
},
} satisfies NextAuthConfig;
This configuration sets the custom sign-in page route.
Protecting Routes with Middleware
To ensure only authenticated users can access specific routes, use Next.js Middleware.
-
Create Middleware
Create a
middleware.ts
file in the root of your project:
// middleware.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
This setup ensures that Middleware runs on the specified paths, protecting the routes by checking authentication before rendering.
Implementing Password Hashing
Hashing passwords adds an extra layer of security by transforming passwords into a fixed-length string.
-
Create an Authentication Module
Create an
auth.ts
file for handling authentication logic:
// auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcrypt';
import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
async function getUser(email: string): Promise<User | undefined> {
try {
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
return user.rows[0];
} catch (error) {
console.error('Failed to fetch user:', error);
throw new Error('Failed to fetch user.');
}
}
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
if (user && await bcrypt.compare(password, user.password)) {
return user;
}
}
return null;
},
}),
],
});
This configuration sets up the Credentials provider and checks if the entered password matches the hashed password stored in the database
Adding Authentication Providers
The Credentials provider allows users to log in with a username and password. While other providers like Google or GitHub are available, i will focus on Credentials for simplicity.
- Update the Authentication Configuration
// auth.ts
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { authConfig } from './auth.config';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
// Configuration for Credentials provider
}),
],
});
Updating the Login Form
To connect the authentication logic with the login form, i use React’s useActionState to handle form submissions and display errors.
1.Create the Login Form
// app/ui/login-form.tsx
'use client';
import { lusitana } from '@/app/ui/fonts';
import { AtSymbolIcon, KeyIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline';
import { ArrowRightIcon } from '@heroicons/react/20/solid';
import { Button } from '@/app/ui/button';
import { useActionState } from 'react';
import { authenticate } from '@/app/lib/actions';
export default function LoginForm() {
const [errorMessage, formAction, isPending] = useActionState(authenticate, undefined);
return (
<form action={formAction} className="space-y-3">
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
<h1 className={`${lusitana.className} mb-3 text-2xl`}>Please log in to continue.</h1>
<div className="w-full">
<div>
<label className="mb-3 mt-5 block text-xs font-medium text-gray-900" htmlFor="email">Email</label>
<div className="relative">
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
id="email"
type="email"
name="email"
placeholder="Enter your email address"
required
/>
<AtSymbolIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
</div>
</div>
<div className="mt-4">
<label className="mb-3 mt-5 block text-xs font-medium text-gray-900" htmlFor="password">Password</label>
<div className="relative">
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
id="password"
type="password"
name="password"
placeholder="Enter password"
required
minLength={6}
/>
<KeyIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
</div>
</div>
</div>
<Button className="mt-4 w-full" aria-disabled={isPending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
</Button>
<div className="flex h-8 items-end space-x-1" aria-live="polite" aria-atomic="true">
{errorMessage && (
<>
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
</>
)}
</div>
</div>
</form>
);
}
This form connects to the authenticate action, displays pending states, and handles error messages.
Adding Logout Functionality
Adding a logout button to the side navigation to enable users to sign out.
1.Update the SideNav Component
// ui/dashboard/sidenav.tsx
import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';
export default function SideNav() {
return (
<div className="flex h-full flex-col px-3 py-4 md:px-2">
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<form
action={async () => {
'use server';
await signOut();
}}
>
<button className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
</button>
</form>
</div>
</div>
);
}
This button calls the signOut
function from auth.ts
to log the user out.
Testing the Implementation
You should now be able to log in and out of the application using the credentials:
Email: user@nextmail.com
Password: 123456
5. Adding Metadata to the Next.js Application
What is Metadata?
Metadata provides additional details about a webpage that are not visible to users but are crucial for search engines and social media platforms. It resides in the <head>
section of your HTML document and helps these systems understand and categorize your content more effectively.
To improve SEO and enhance social media presence, I incorporated metadata into the application. Metadata, embedded within the
element of HTML, provides search engines and social media platforms with additional information about the page. This included adding Open Graph tags to optimize link previews on social media.Adding Metadata in Next.js
Next.js provides a Metadata API that allows you to configure metadata in a flexible and organized manner. You can add metadata either through configuration files or dynamically within your components.
Config-Based Metadata
define static metadata in your layout.js or page.js files. Here’s how you can set up basic metadata:
- Root Layout Metadata
In the root
layout.tsx
file, define a metadata object:
// app/layout.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Acme Dashboard',
description: 'The official Next.js Course Dashboard, built with App Router.',
metadataBase: new URL('https://next-learn-dashboard.vercel.sh'),
};
export default function RootLayout() {
// ...
}
This metadata will be applied globally to all pages that use this layout.
2.Page-Specific Metadata
mmh, so To override or add metadata specific to a page, define the metadata object in the page file:
// app/dashboard/invoices/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Invoices | Acme Dashboard',
};
This page-level metadata will override the global metadata for this specific page.
Dynamic Page Titles with Templates
To avoid repetitive metadata and manage dynamic titles more efficiently, i usedt the title.template
field:
yeey !!
- Set Up a Template in the Root Layout
// app/layout.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: {
template: '%s | Acme Dashboard',
default: 'Acme Dashboard',
},
description: 'The official Next.js Learn Dashboard built with App Router.',
metadataBase: new URL('https://next-learn-dashboard.vercel.sh'),
};
The %s placeholder will be replaced with the page-specific title.
- Use the Template in a Page
// app/dashboard/invoices/page.tsx
export const metadata: Metadata = {
title: 'Invoices',
};
This setup results in a page title like "Invoices | Acme Dashboard."
The past two weeks have been a period of significant learning and development. By employing a range of technologies and best practices, I have built a robust and efficient Next.js dashboard application. From optimizing performance to implementing secure data handling, each task has contributed to a comprehensive and scalable solution. This project not only enhanced my technical skills but also provided practical experience in building modern web applications.
Top comments (0)