DEV Community

Cover image for Rapid development of full-stack navigation sites using Nextjs
wangfengyuan
wangfengyuan

Posted on

Rapid development of full-stack navigation sites using Nextjs

Background

With the hotness of ChatGPT, many developers responded quickly with a spurt of AI applications applied to different scenarios, and basically focused on web domain applications, and behind the rapid development, we can see that developers mostly choose Next.js or Nuxt.js full-stack frameworks to develop in order to quickly validate their products. The main reasons behind this selection, I think, are

  • The importance of SEO
    Foreign countries pay more attention to the importance of SEO, domestic search engines mostly rely on money to buy search traffic, including small programs, App such as the demand for SEO is not much

  • The rise of Edge Function

    Serverless enables front-end development to quickly develop full-stack applications and conveniently host their own back-end services without paying too much attention to deployment, however, his disadvantage is that most Serverless are using containerized solutions, so the cold start time is long, if the request is forwarded in their own cloud function OpenAI interface, it may happen that the request time is very long. Nowadays, vendors such as Vercel, CloudFlare, Supabase, etc. have Edge Function capability, which allows the function to run in some edge nodes closer to the user. For faster cold start time to respond to users quickly, this solution generally also adds some restrictions, but still favored by many developers

  • Diversity of Cloud Service Vendors

    Cloud service vendors provide many basic services, and when hosting projects with services such as Vercel, they can integrate with Github for continuous deployment and also assign a domain name. Many other vendors also offer a number of free back-end storage services, such as:

These vendors' free plans are perfectly adequate for personal development, but you can of course use paid plans depending on the size of the product

And the purpose of this article is to try to develop a simple navigation page to satisfy my own collection fetish for freeing my favorites, to learn Next.js development, and to experience the ease of developing full-stack applications brought by Next.js.

Initialize the project

With the emergence of atomized CSS solutions like tailwindcss and unocss, there are many UI component libraries derived from them, such as Radix, daisyUI, flowbite, of which the RadixUI component library pays much attention to web accessibility, and the components follow the WAI-ARIA standard, which makes it easier for developers to build accessible UI interfaces, and because it focuses on accessibility and belongs to Headless UI which means no specific style class name code, shadcnThe author of shadcn/ui developed a component library, which is based on the RadixUI component library and gives a simple and beautiful style, which is favored by many developers.

Here is a direct clone of the author's Nextjs template initialization project

git clone https://github.com/shadcn/next-template
Enter fullscreen mode Exit fullscreen mode

The project uses the latest app router version of Next.js and has integrated tailwindcss and shadcn/ui component libraries. Here we choose to do the navigation site also because it is simple enough, the key style is for the sidebar, because tailwindcss is mobile first, so here set the default hidden, when the screen width is larger than sm show.

<div className="fixed z-20 hidden min-h-screen sm:block">
    ...
</div>
Enter fullscreen mode Exit fullscreen mode

And for the list area, use grid layout, default mobile priority one column, depending on the screen size to display 2 or 3 columns

<div className="grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-6 lg:grid-cols-3">
   ...
</div>
Enter fullscreen mode Exit fullscreen mode

Other styles can be borrowed from other people's website design simply beautify the next, for not familiar with css and lack of aesthetic I spent a lot of time in adjusting, always feel that the site is not beautiful enough, but do not know how to beautify.

Database integration

Define the model

Database integration here I chose Prisma, similar to the more popular and Drizzle, for Prisma specific concepts, use and its advantages, you can refer to my organized record of notes.

npx prisma init
Enter fullscreen mode Exit fullscreen mode

will create the prisma/schema.prisma file, creating the model as follows

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url = env("DATABASE_URL")
}

model Category {
  id String @id @default(cuid())
  icon String
  title String
  description String
  rank Int?
  createdAt DateTime @default(now()) @map(name: "created_at")
  updatedAt DateTime @default(now()) @map(name: "updated_at")
  links Link[]

  @@map(name: "category")
}

model Link {
  id          String   @id @default(cuid())
  icon        String
  url         String
  title       String
  description String
  rank        Int?
  public      Boolean  @default(true)
  status      Int      @default(1) @db.TinyInt
  createdAt   DateTime @default(now()) @map(name: "created_at")
  updatedAt   DateTime @default(now()) @map(name: "updated_at")
  cid         String
  catagory    Category @relation(fields: [cid], references: [id])

  @@map(name: "link")
}
Enter fullscreen mode Exit fullscreen mode

Where DATABASE_URL is the remote database address, here I used MySQL from PlantScale. Of course you can also use PostgreSQL from Supabase or other databases, create a .env file and fill in the service address

https://cos.codefe.top/images/planetscale-connect-setting.png

DATABASE_URL='mysql://user:password@aws.connect.psdb.cloud/dev?sslaccept=strict'
Enter fullscreen mode Exit fullscreen mode

The relationship diagram can be seen in this visualization Prisma model website as follows, representing category instances and web link instances respectively

https://cos.codefe.top/images/frontend-nav-prisma-model.png

Table Sync

Then execute the command to synchronize the local model to the database table

npx prisma db push
Enter fullscreen mode Exit fullscreen mode

Then you may encounter the following error

https://cos.codefe.top/images/prisma_db_push_error.png

After checking the website, I found that the official website in documentation that the MySQL database of PlanetScale does not support foreign keys, you need to specify relationMode specially

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}
Enter fullscreen mode Exit fullscreen mode

The default value of relationMode is foreignKeys, this option should be enabled when using the MySQL connector of the PlanetScale database to simulate relationships in the Prisma Client. Execute the db push command again to synchronize the model to the database

https://cos.codefe.top/images/prisma_db_push.png

Insert and query data

Then execute

npx prisma studio
Enter fullscreen mode Exit fullscreen mode

Open the table editor to add your own data

! https://cos.codefe.top/images/frontend-studio.png

Execute the command to generate the PrismaClient instance

pnpm install @prisma/client
npx prisma generate
Enter fullscreen mode Exit fullscreen mode

Then you can query the data by relation at once

import prisma from '@/lib/db';
import type { Prisma } from '@prisma/client';

export default async function getNavLinks() {
  const res = await prisma.category.findMany({
    orderBy: [
      {
        rank: 'asc',
      }
    ],
    include: {
      links: {
        orderBy: {
          rank: 'asc',
        },
        where: {
          public: true,
          status: 1,
        },
      },
    },
  });
  return res;
}

export type CategoryWithLinks = Prisma.PromiseReturnType<typeof getNavLinks>
Enter fullscreen mode Exit fullscreen mode

User Authentication

Integrating user authentication in Next.js is very simple and can be done directly using NextAuth

NextAuth

NextAuth is an open source authentication solution for Next.js applications. By default, NextAuth uses JSON Web Tokens (JWT) to save user sessions. nextAuth supports popular login services such as Google, Facebook, Auth0, Apple, email, and OAuth 1.0 and 2.0 services, among others, and is a flexible and configurable authentication solution.

First install the required dependencies

pnpm install next-auth @next-auth/prisma-adapter
Enter fullscreen mode Exit fullscreen mode

Add user-related models according to the official documentation guidelines

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}
Enter fullscreen mode Exit fullscreen mode

*GitHub*

Obtain the Github client ID, reference document, which contains the following steps:

  1. Open the GitHub application page and click Create Github application.

    ! https://cos.codefe.top/images/github_setting_newapp.png

  2. Enter the name, homepage, and callback address after authorization. Here, fill in localhost:3000 for the development environment, and switch to your own online domain name when you go live.

    ! https://cos.codefe.top/images/github_setting_newapp_dev.png

  3. Click "Generate" and save Client ID and Client Secret.

    https://cos.codefe.top/images/github_clientid_dev.png

Google

Refer to the NextAuth documentation guidelines at Google developer site Register the application and select the api service

! https://cos.codefe.top/images/google-oauth-demo.png

Select Web application for the application type, similar to Github fill in the trusted domain name and callback address to confirm

https://cos.codefe.top/images/google_oauth_setting.png

Create API Routing

First add the authentication-related keys saved above to the .env file, where NEXTAUTH_SECRET is the user's key for generating JWT

# google login
GOOGLE_CLIENT_ID="GOOGLE_CLIENT_ID"
GOOGLE_CLIENT_SECRET="GOOGLE_CLIENT_SECRET"

# github登录
GITHUB_CLIENT_ID="GITHUB_CLIENT_ID"
GITHUB_CLIENT_SECRET="GITHUB_CLIENT_SECRET"

NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="webnav"
Enter fullscreen mode Exit fullscreen mode

You can see that the API callbacks above are /api/auth/github and /api/auth/google respectively. Create the app/api/auth/[...nestauth]/route.ts file, and add the following code:

import NextAuth, { type NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GitHubProvider from "next-auth/providers/github";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
import { compare } from "bcrypt";

const prisma = new PrismaClient();

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GitHubProvider({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        const { email, password } = credentials ?? {}
        if (!email || !password) {
          throw new Error("Missing username or password");
        }
        const user = await prisma.user.findUnique({
          where: {
            email: credentials?.email,
          },
        });
        // if user doesn't exist or password doesn't match
        if (!user || !(await compare(password, user.password!))) {
          throw new Error("Invalid username or password");
        }
        return user;
      },
    })
  ],
  session: {
    strategy: "jwt",
  },
  debug: process.env.NODE_ENV !== "production",
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
Enter fullscreen mode Exit fullscreen mode

Among them, GitHubProvider user Github login, GoogleProvider for Google login, CredentialsProvider for custom login, here will check the email password, match it and return the user information.

Also need to create the registration page, create app/login/page.tsx file and app/register/page.tsx file, page directly copied from taxonomy page style, own Add google login button, the effect is as shown

! https://cos.codefe.top/images/wennav-login-shot.png

On the page by

import { signIn, signOut } from "next-auth/react"
Enter fullscreen mode Exit fullscreen mode

The signIn method allows logging in with a Google or GitHub account. signOut allows logging out of a user session, see the official documentation for this section

Deployment

Import the project directly from [Vercel](https://vercel.com/) and modify the build command to

npx prisma generate && next build
Enter fullscreen mode Exit fullscreen mode

before build, otherwise the compilation will fail due to type error, and add the required environment variables in .env

https://cos.codefe.top/images/webnav-env-setting.png

At the same time, because our data source is in the database, and Nextjs is by default build-time page generation which is SSG mode, we need to update the page content when the database has data updates, so we need to use its Incremental Static Generation ISR (Incremental Static Regeneration) mode, refer to official documentation (https://nextjs.org/docs/app/building-your-application/data-fetching/revalidating#background-revalidation), add the export in page.tsx, here the update The timeliness requirement is not high, so set it to 1 day

export const revalidate = 24 * 60 * 60.
Enter fullscreen mode Exit fullscreen mode

You can see from the build log that it has taken effect

https://cos.codefe.top/images/nextjs_vercel_deploy_isr.png

After successful deployment, bind the custom domain name and finish sprinkling.

Summary

This article to a simple navigation site for example, combined with Next.js, Prisma, NextAuth, shadcn/ui to learn how to build full-stack applications, you can open final website page, you can also in this project open source address to view the full code, wrir article is not easy, welcome star, thanks.

Top comments (0)