Skip to content

Commit e559318

Browse files
author
misostack
committed
advanced routing
1 parent fe5c563 commit e559318

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
---
2+
title: "Khoá học NextJS Bài 03 - Advanced Routing"
3+
type: "post"
4+
date: 2024-05-20T08:51:24+07:00
5+
description: "Nội dung chính trong bài này sẽ về phân quyền, chuyển hướng, layout, loading screen"
6+
keywords: ["nextjs tutorial", "nextjs routing"]
7+
categories: ["nextjs-tutorial"]
8+
tags: ["nextjs"]
9+
image: "https://user-images.githubusercontent.com/31009750/246866968-e42afc31-8eea-44e8-ba86-629918f50401.png"
10+
---
11+
12+
**Note**: Toàn bộ mã nguồn của khóa học này đều được công khai trên github tại [Nextjs Tutorial 2024](https://github.com/nextjsvietnam/nextjs-tutorial-2024/tree/tutorial/lession-03)
13+
14+
Các nội dung chính trong bài học lần này:
15+
16+
1. Phân quyền và chuyển hướng
17+
2. Tạo layout dùng chung
18+
3. Loading Screen
19+
20+
Với nội dung trên, chúng ta sẽ tiếp tục với việc phân quyền và chuyển hướng, cũng như thực hành chia layout dùng chung, hiển thị màn hình chờ ( loading screen )
21+
22+
![image](https://gist.github.com/assets/31009750/5504676f-fb7b-46b1-aba6-3d3b8a9cd794)
23+
24+
Yêu cầu:
25+
26+
1. Đối với các trang thuộc nhóm Private chỉ có khách hàng đã đăng nhập mới có thể truy cập được.
27+
Trường hợp người dùng cố tình vào bằng các địa chỉ cố định, chuyển hướng người dùng sang trang đăng nhập.
28+
2. Đối với user đã đăng nhập, khi vào lại các trang liên quan tới đăng nhập như: login,register,forget password, reset password, hãy chuyển hướng họ tới trang my-account
29+
3. Các trang thuộc nhóm Private của khách hàng có giao diện chung, chỉ khác phần nội dung (content).
30+
4. Các trang thuộc nhóm đăng nhập, cũng có giao diện chung, chỉ khác phần nội dung (content).
31+
5. Toàn bộ các trang trong website đều sử dụng chung phần header, footer, khác phần nội dung chính (main content)
32+
33+
Trong bài này, chúng ta sẽ giả lập rằng sau khi khách hàng đăng nhập xong, các thông tin sẽ được lưu lại trên cookie session.
34+
35+
Do đó chúng ta sẽ cài đặt logic như sau:
36+
37+
- Nếu user truy cập vào các trang thuộc nhóm private => kiểm tra cookie này có tồn tại hay không, nếu có cho phép user truy cập, ngược lại thì chuyển hướng user sang trang login
38+
39+
Trong NextJS chúng ta có thể làm như sau:
40+
41+
1. Chuyển hướng người dùng trong trang sử dụng **redirect** function
42+
43+
Đầu tiên tôi sẽ tạo lần lượt các trang bổ sung như sau: login, register, forgot-password, reset-password.
44+
45+
Để nhóm các trang này trong một group trong folder, nextjs hỗ trợ chúng ta tạo folder với cú pháp (folderName) , khi dùng cú pháp này folder được tạo ra sẽ không được tính vào cây thư mục khi nextjs route thực hiện mapping giữa segment và cấu trúc thư mục.
46+
47+
![image](https://gist.github.com/assets/31009750/a5eaef32-cef7-4c06-8701-517abc4921be)
48+
49+
```ts
50+
// shared/helpers.ts
51+
import { COOKIE_PREFIX } from "./constant";
52+
53+
export const setCookie = (cname: string, cvalue: string, exdays: number) => {
54+
if (exdays) {
55+
const d = new Date();
56+
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
57+
let expires = "expires=" + d.toUTCString();
58+
59+
document.cookie =
60+
`${COOKIE_PREFIX}_${cname}` + "=" + cvalue + ";" + expires + ";path=/";
61+
} else {
62+
document.cookie = `${COOKIE_PREFIX}_${cname}` + "=" + cvalue + ";path=/";
63+
}
64+
};
65+
66+
export const getCookie = (cname: string) => {
67+
let name = `${COOKIE_PREFIX}_${cname}` + "=";
68+
let ca = document.cookie.split(";");
69+
for (let i = 0; i < ca.length; i++) {
70+
let c = ca[i];
71+
while (c.charAt(0) == " ") {
72+
c = c.substring(1);
73+
}
74+
if (c.indexOf(name) == 0) {
75+
return c.substring(name.length, c.length);
76+
}
77+
}
78+
return "";
79+
};
80+
81+
export const deleteCookie = (cname: string) => {
82+
let name = `${COOKIE_PREFIX}_${cname}` + "=";
83+
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
84+
};
85+
```
86+
87+
```ts
88+
// shared/auth.ts
89+
90+
import { redirect } from "next/navigation";
91+
import { getCookie } from "./helper";
92+
import { AppRoute, CookieName } from "./constant";
93+
94+
export const runUserGuard = () => {
95+
let err = null;
96+
try {
97+
const userCookie = getCookie(CookieName.UserCookie);
98+
if (userCookie) {
99+
const user = JSON.parse(userCookie);
100+
return true;
101+
}
102+
} catch (err) {
103+
err = err;
104+
}
105+
if (err) {
106+
alert(err);
107+
}
108+
redirect(AppRoute.Login);
109+
};
110+
```
111+
112+
```tsx
113+
// my-account/page.tsx
114+
import { runUserGuard } from "@/shared/auth";
115+
116+
export default function MyAccount() {
117+
// run user Guard
118+
runUserGuard();
119+
120+
return (
121+
<main className="container-xl mx-auto p-4">
122+
<h1>My Account</h1>
123+
</main>
124+
);
125+
}
126+
```
127+
128+
Các bạn hãy áp dụng cho các trang còn lại.
129+
...
130+
131+
Nhưng nếu tôi có hơn 10 trang như vậy thì sao, còn cách nào khác không?
132+
133+
1. Nhóm các trang thuộc my account chung 1 nhóm sử dụng chung layout
134+
135+
```
136+
my-account
137+
page.tsx
138+
layout.tsx
139+
orders
140+
page.tsx
141+
[id]
142+
page.tsx
143+
```
144+
145+
Do đó, chúng ta cần điều chỉnh lại đường dẫn của mình.
146+
147+
![image](https://gist.github.com/assets/31009750/bbc6c22e-68d4-4407-a4c9-587528ca0c2a)
148+
149+
Nhưng nếu có nhiều hơn 1 nhóm như vậy, liệu chúng ta có cách nào không?
150+
151+
2. Sử dụng middleware
152+
153+
![image](https://gist.github.com/assets/31009750/b4dda3c1-5497-425f-9da4-b1b478597a6f)
154+
155+
```ts
156+
import AuthService from "@/service/auth.service";
157+
import { AppCookie, AppRoute } from "@/shared/constant";
158+
import { NextRequest, NextResponse } from "next/server";
159+
160+
export const authMiddleware = (req: NextRequest) => {
161+
// middleware/auth.ts
162+
163+
const token = req.cookies.get(AppCookie.UserToken);
164+
const authService = new AuthService();
165+
const userToken = token?.name ? authService.verifyToken(token?.value) : null;
166+
167+
// Assuming you have some function to verify the token
168+
if (!token || !userToken) {
169+
return NextResponse.redirect(new URL(AppRoute.Login, req.url));
170+
}
171+
172+
return NextResponse.next();
173+
};
174+
```
175+
176+
```ts
177+
import { NextResponse } from "next/server";
178+
import type { NextRequest } from "next/server";
179+
import { authMiddleware } from "./app/middlewares/auth.middleware";
180+
import { ProtectedRoutes } from "./shared/constant";
181+
182+
// This function can be marked `async` if using `await` inside
183+
export function middleware(req: NextRequest) {
184+
console.log("[Middleware Demo] : " + req.url);
185+
186+
const path = req.nextUrl.pathname;
187+
if (ProtectedRoutes.some((route) => path.startsWith(route))) {
188+
// apply auth middleware
189+
const redirectResponse = authMiddleware(req);
190+
if (redirectResponse) {
191+
return redirectResponse;
192+
}
193+
}
194+
195+
return NextResponse.next();
196+
}
197+
198+
// See "Matching Paths" below to learn more
199+
export const config = {
200+
matcher: [
201+
/*
202+
* Match all request paths except for the ones starting with:
203+
* - _next/static (static files)
204+
* - _next/image (image optimization files)
205+
* - favicon.ico (favicon file)
206+
*/
207+
{
208+
source: "/((?!_next/static|_next/image|favicon.ico).*)",
209+
missing: [
210+
{ type: "header", key: "next-router-prefetch" },
211+
{ type: "header", key: "purpose", value: "prefetch" },
212+
],
213+
},
214+
215+
{
216+
source: "/((?!_next/static|_next/image|favicon.ico).*)",
217+
has: [
218+
{ type: "header", key: "next-router-prefetch" },
219+
{ type: "header", key: "purpose", value: "prefetch" },
220+
],
221+
},
222+
223+
{
224+
source: "/((?!_next/static|_next/image|favicon.ico).*)",
225+
has: [{ type: "header", key: "x-present" }],
226+
missing: [{ type: "header", key: "x-missing", value: "prefetch" }],
227+
},
228+
],
229+
};
230+
```
231+
232+
```ts
233+
export const ProtectedRoutes = ["/my-account"];
234+
```
235+
236+
Giờ chúng ta thử thực hiện login

0 commit comments

Comments
 (0)