DEV Community

Tribal Chief for Swahilipot Developers

Posted on

Nextjs-Dashboard Project .

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

the image of the financial dashboard

Image description

Image description

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

** 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

  1. Access Your Vercel Dashboard:
    Navigate to your project’s dashboard on Vercel.
    Click "Continue to Dashboard" and select the "Storage" tab.

  2. 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.

  3. 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.

Important: Ensure .env is listed in your .gitignore file to prevent exposing your database secrets in version control.

  1. Install the Vercel Postgres SDK:

    Run the following command to install the Vercel Postgres SDK:

pnpm i @vercel/postgres

Enter fullscreen mode Exit fullscreen mode

Seed Your Database

Seeding your database involves populating it with initial data to test your application.

  1. Prepare the Seed Script:

    • Inside the /app directory, locate and uncomment the seed folder. This folder contains route.ts, a Next.js Route Handler used to seed your database.
  2. Run the Seed Script:
    Ensure your local development server is running with:

pnpm run dev

Enter fullscreen mode Exit fullscreen mode

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.

  1. Install NextAuth.js

Run the following command to install the beta version compatible with Next.js 14:

pnpm i next-auth@beta

Enter fullscreen mode Exit fullscreen mode

2.Generate a Secret Key

Generate a secret key for encrypting cookies:

openssl rand -base64 32

Enter fullscreen mode Exit fullscreen mode

Add this key to your .env file:

AUTH_SECRET=your-secret-key

Enter fullscreen mode Exit fullscreen mode
  1. 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;

Enter fullscreen mode Exit fullscreen mode

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.

  1. 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$).*)'],
};

Enter fullscreen mode Exit fullscreen mode

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.

  1. 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;
      },
    }),
  ],
});

Enter fullscreen mode Exit fullscreen mode

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.

  1. 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
    }),
  ],
});

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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() {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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',
};
Enter fullscreen mode Exit fullscreen mode

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 !!

  1. 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'),
};
Enter fullscreen mode Exit fullscreen mode

The %s placeholder will be replaced with the page-specific title.

  1. Use the Template in a Page // app/dashboard/invoices/page.tsx
export const metadata: Metadata = {
  title: 'Invoices',
};
Enter fullscreen mode Exit fullscreen mode

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)