Next.js 保護目錄的用法


嘿,大家好!今天我們來聊聊一個非常實用的話題——在 Next.js 中如何設置受保護的目錄。這個技巧對於任何需要身份驗證的應用來說,都是至關重要的。那麼,讓我們開始吧!

為什麼需要保護目錄?

在現代網頁應用中,我們常常需要限制某些頁面的訪問權限,確保只有已經通過身份驗證的用戶才能進入。例如,管理後台、用戶資料頁面或其他敏感信息頁面。如果沒有適當的保護機制,這些頁面可能會暴露給未經授權的用戶,造成安全風險。

基本概念

在 Next.js 中,我們可以使用 React Context 和高階組件(Higher Order Component, HOC)來實現受保護的路由。通過這種方式,我們可以將身份驗證邏輯集中管理,保持代碼清晰和模塊化。

目錄結構

首先,我們需要一個清晰的目錄結構來組織受保護的頁面。以下是我們推薦的結構:

project-root/
├── app/
│   ├── (protected)/
│   │   ├── dashboard.tsx
│   │   ├── settings.tsx
│   │   ├── profile.tsx
│   ├── public/
│   │   ├── index.tsx
│   │   ├── about.tsx
│   │   ├── contact.tsx
├── components/
│   ├── ProtectedRoute.tsx
│   ├── Layout.tsx
├── context/
│   ├── AuthContext.tsx
├── pages/
│   ├── _app.tsx
├── store/
├── styles/
└── tsconfig.json

創建 AuthContext

首先,我們需要創建一個身份驗證上下文(AuthContext)來管理用戶的身份驗證狀態。

// context/AuthContext.tsx
import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';

interface AuthContextProps {
  isAuthenticated: boolean;
  login: () => void;
  logout: () => void;
}

const AuthContext = createContext<AuthContextProps | undefined>(undefined);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    const authState = localStorage.getItem('isAuthenticated');
    setIsAuthenticated(authState === 'true');
  }, []);

  const login = () => {
    setIsAuthenticated(true);
    localStorage.setItem('isAuthenticated', 'true');
  };

  const logout = () => {
    setIsAuthenticated(false);
    localStorage.setItem('isAuthenticated', 'false');
  };

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

創建 ProtectedRoute 高階組件

接下來,我們創建一個高階組件來保護受限的頁面。這個組件將檢查用戶是否已經登錄,如果沒有,則重定向到登錄頁面。

// components/ProtectedRoute.tsx
import React from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '../context/AuthContext';

const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
  const { isAuthenticated } = useAuth();
  const router = useRouter();

  React.useEffect(() => {
    if (!isAuthenticated) {
      router.push('/login'); // 未認證的用戶重定向到登錄頁面
    }
  }, [isAuthenticated, router]);

  if (!isAuthenticated) {
    return null; // 或者返回一個加載中的組件
  }

  return <>{children}</>;
};

export default ProtectedRoute;

_app.tsx 中使用 AuthProvider 和 ProtectedRoute

我們需要在 _app.tsx 中使用 AuthProviderProtectedRoute,以便所有頁面都能夠訪問身份驗證狀態。

// pages/_app.tsx
import '@/styles/globals.css';
import type { AppProps } from 'next/app';
import { Provider } from 'react-redux';
import store from '../store';
import { AuthProvider } from '@/context/AuthContext';
import ProtectedRoute from '@/components/ProtectedRoute';

const MyApp: React.FC<AppProps> = ({ Component, pageProps, router }) => {
  const isProtectedRoute = router.pathname.startsWith('/app/(protected)');

  return (
    <Provider store={store}>
      <AuthProvider>
        {isProtectedRoute ? (
          <ProtectedRoute>
            <Component {...pageProps} />
          </ProtectedRoute>
        ) : (
          <Component {...pageProps} />
        )}
      </AuthProvider>
    </Provider>
  );
};

export default MyApp;

動態加載受保護的組件

為了優化加載性能,我們可以使用 React 的 lazySuspense 來動態加載受保護的組件。

// routes/index.ts
import { lazy } from 'react';

const Dashboard = lazy(() => import('@/app/(protected)/dashboard'));
const Settings = lazy(() => import('@/app/(protected)/settings'));
const Profile = lazy(() => import('@/app/(protected)/profile'));

const routes = [
  {
    path: '/dashboard',
    component: Dashboard,
  },
  {
    path: '/settings',
    component: Settings,
  },
  {
    path: '/profile',
    component: Profile,
  },
];

export default routes;

總結

通過這樣的結構,我們可以有效地保護應用中的敏感頁面,確保只有已經通過身份驗證的用戶才能訪問。這不僅提高了應用的安全性,還保持了代碼的清晰和模塊化。希望這篇文章能幫助你在 Next.js 中更好地管理受保護的路由。如果有任何問題或建議,歡迎在評論區留言!


希望這篇文章對你有所幫助!如果你有任何問題或需要進一步的指導,隨時與我聯繫。祝你編碼愉快!