I recently develeoped a full-stack project using Next.js. Despite encountering many issues, the overall development experience was good. For example, convenient anthentication, Server Actions, and built-in tools.
This project is a photography portfolio showcase website primarily built using Next.js, Prisma, PostgreSQL, ShadcnUI, TailwindCSS, and TypeScript etc.
Project Introduce
Gallery Lantern is a Plog website where I share photographs in my daily lives. The website uses serveral tools: Auth.js for user authentication, next-themes for dark mode switching, and exifr for analyzing photo parameters. Additionally, it integrates geocoding to query addresses and utilizes GPT-4 to generate titles and descriptions for images based on the content. All photographs are stored on Cloudflare R2.
Process
Route Interception
In the previous article, it was mentioned that Auth.js is used for authentication. Centainly, the administration page should not be accessible to everyone, hence the need for interception. Unlike React Router, Next.js allows for the writing of code directly within the middleware.ts
.
export default function middleware(
request: NextRequest,
response: NextResponse
) {
const pathname = request.nextUrl.pathname;
if (pathname === PATH_ADMIN) {
return NextResponse.redirect(
new URL(PATH_ADMIN_PHOTOS, request.url)
);
}
return auth(
request as unknown as NextApiRequest,
response as unknown as NextApiResponse
);
}
In addition to, it is necessary to supplement the callbacks option in NextAuth(creating an Authentication method) for the purpose of determining route permissions.
export const {
handlers: { GET, POST },
signIn,
signOut,
auth,
} = NextAuth({
//...
callbacks: {
authorized({ auth, request }) {
const { pathname } = request.nextUrl;
const isUrlProtected =
isPathProtected(pathname);
const isUserLogin = !!auth?.user;
const isAuthorized =
!isUrlProtected || isUserLogin;
return isAuthorized;
},
},
// ...
});
At now, without logging in, access to management page is impossible.
Prisma Client
Here, I will not dive into the introduce and installion of PostgreSQL. After installing the prisma dependency, executing npx prisma init
into initialize the schema.prisma
file, which is where data models are defined. Additionally, adding environment variables(such as database link address) to the .env.local
file.
Currently, installing prisma client for accessing the database.
npm install @prisma/client
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Photo {
id Int @id @default(autoincrement())
title String @db.VarChar(255)
url String @db.VarChar(255)
}
- If
schema
is modified, runnpx prisma migrate dev
to synchronize the changes with the database. The migrate command will automatically update the Prisma Client, so it is necessary to runprisma generate
again.
Finally, avoiding instantiating extra PrismaClient
instances. The solution is in this case to instantiate a single instance PrismaClient
and save it on the globalThis object.
import { PrismaClient } from "@prisma/client";
let prisma: PrismaClient;
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient();
} else {
let globalWithPrisma =
global as typeof globalThis & {
prisma: PrismaClient;
};
if (!globalWithPrisma.prisma) {
globalWithPrisma.prisma = new PrismaClient();
}
prisma = globalWithPrisma.prisma;
}
export default prisma;