Next.js 应用程序的一般架构
典型文件结构
一个标准的 Next.js 项目遵循特定的文件和目录结构,以便于其路由、API 端点和静态资产管理等功能。以下是一个典型的布局:
Copy my - nextjs - app /
├── node_modules /
├── public /
│ ├── images /
│ │ └── logo.png
│ └── favicon.ico
├── app /
│ ├── api /
│ │ └── hello /
│ │ └── route.ts
│ ├── layout.tsx
│ ├── page.tsx
│ ├── about /
│ │ └── page.tsx
│ ├── dashboard /
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components /
│ │ ├── Header.tsx
│ │ └── Footer.tsx
│ ├── styles /
│ │ ├── globals.css
│ │ └── Home.module.css
│ └── utils /
│ └── api.ts
├── .env. local
├── next .config.js
├── tsconfig.json
├── package.json
├── README.md
└── yarn.lock / package - lock.json
核心目录和文件
public/: 托管静态资产,如图像、字体和其他文件。这里的文件可以在根路径 (/
) 访问。
app/: 应用程序页面、布局、组件和 API 路由的中央目录。采用 App Router 范式,支持高级路由功能和服务器-客户端组件分离。
app/layout.tsx: 定义应用程序的根布局,包裹所有页面并提供一致的 UI 元素,如页眉、页脚和导航栏。
app/page.tsx: 作为根路由 /
的入口点,渲染主页。
app/[route]/page.tsx: 处理静态和动态路由。app/
中的每个文件夹代表一个路由段,而这些文件夹中的 page.tsx
对应于该路由的组件。
app/api/: 包含 API 路由,允许您创建处理 HTTP 请求的无服务器函数。这些路由替代了传统的 pages/api
目录。
app/components/: 存放可重用的 React 组件,可以在不同页面和布局中使用。
app/styles/: 包含全局 CSS 文件和用于组件范围样式的 CSS 模块。
app/utils/: 包含实用函数、辅助模块和其他可以在应用程序中共享的非 UI 逻辑。
.env.local: 存储特定于本地开发环境的环境变量。这些变量 不 会提交到版本控制。
next.config.js: 自定义 Next.js 行为,包括 webpack 配置、环境变量和安全设置。
tsconfig.json: 配置项目的 TypeScript 设置,启用类型检查和其他 TypeScript 功能。
package.json: 管理项目依赖、脚本和元数据。
README.md: 提供有关项目的文档和信息,包括设置说明、使用指南和其他相关细节。
yarn.lock / package-lock.json: 将项目的依赖锁定到特定版本,确保在不同环境中的一致安装。
Next.js 中的客户端
app
目录中的基于文件的路由
app
目录是最新 Next.js 版本中路由的基石。它利用文件系统来定义路由,使路由管理直观且可扩展。
处理根路径 /文件结构:
Copy my-nextjs-app/
├── app/
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
关键文件:
app/page.tsx
:处理对根路径 /
的请求。
app/layout.tsx
:定义应用程序的布局,包裹所有页面。
实现:
Copy tsxCopy code // app/page.tsx
export default function HomePage () {
return (
< div >
< h1 >Welcome to the Home Page!</ h1 >
< p >This is the root route.</ p >
</ div >
);
}
解释:
路由定义: app
目录下的 page.tsx
文件对应于 /
路由。
布局集成: HomePage
组件被 layout.tsx
包裹,可以包含头部、底部和其他公共元素。
处理其他静态路径示例: /about
路由
文件结构:
Copy arduinoCopy codemy-nextjs-app/
├── app/
│ ├── about/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
实现:
Copy // app/about/page.tsx
export default function AboutPage () {
return (
< div >
< h1 >About Us</ h1 >
< p >Learn more about our mission and values.</ p >
</ div >
);
}
解释:
路由定义: about
文件夹中的 page.tsx
文件对应于 /about
路由。
动态路由动态路由允许处理具有可变段的路径,使应用程序能够根据参数(如 ID、slug 等)显示内容。
示例: /posts/[id]
路由
文件结构:
Copy arduinoCopy codemy-nextjs-app/
├── app/
│ ├── posts/
│ │ └── [id]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
实现:
Copy tsxCopy code // app/posts/[id]/page.tsx
import { useRouter } from 'next/navigation' ;
interface PostProps {
params : { id : string };
}
export default function PostPage ({ params } : PostProps ) {
const { id } = params;
// Fetch post data based on 'id'
return (
< div >
< h1 >Post #{id}</ h1 >
< p >This is the content of post {id}.</ p >
</ div >
);
}
解释:
动态段: [id]
表示路由中的动态段,从 URL 中捕获 id
参数。
访问参数: params
对象包含动态参数,可以在组件内访问。
路由匹配: 任何匹配 /posts/*
的路径,例如 /posts/1
、/posts/abc
等,将由此组件处理。
嵌套路由Next.js 支持嵌套路由,允许创建与目录结构相对应的层次化路由结构。
示例: /dashboard/settings/profile
路由
文件结构:
Copy arduinoCopy codemy-nextjs-app/
├── app/
│ ├── dashboard/
│ │ ├── settings/
│ │ │ └── profile/
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
实现:
Copy tsxCopy code // app/dashboard/settings/profile/page.tsx
export default function ProfileSettingsPage () {
return (
< div >
< h1 >Profile Settings</ h1 >
< p >Manage your profile information here.</ p >
</ div >
);
}
解释:
深层嵌套: dashboard/settings/profile/
目录下的 page.tsx
文件对应于 /dashboard/settings/profile
路由。
层次反映: 目录结构反映了 URL 路径,增强了可维护性和清晰度。
捕获所有路由捕获所有路由处理多个嵌套段或未知路径,提供路由处理的灵活性。
示例: /*
路由
文件结构:
Copy my-nextjs-app/
├── app/
│ ├── [..slug]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
实施:
Copy // app/[...slug]/page.tsx
interface CatchAllProps {
params : { slug : string [] };
}
export default function CatchAllPage ({ params } : CatchAllProps ) {
const { slug } = params;
const fullPath = `/ ${ slug .join ( '/' ) } ` ;
return (
< div >
< h1 >Catch-All Route</ h1 >
< p >You have navigated to: {fullPath}</ p >
</ div >
);
}
解释:
Catch-All 段: [...slug]
捕获所有剩余的路径段作为数组。
用法: 适用于处理动态路由场景,如用户生成的路径、嵌套类别等。
路由匹配: 像 /anything/here
、/foo/bar/baz
等路径由此组件处理。
潜在的客户端漏洞
虽然 Next.js 提供了安全的基础,但不当的编码实践可能会引入漏洞。关键的客户端漏洞包括:
跨站脚本攻击 (XSS)XSS 攻击发生在恶意脚本被注入到受信任的网站时。攻击者可以在用户的浏览器中执行脚本,窃取数据或代表用户执行操作。
易受攻击代码示例:
Copy // Dangerous: Injecting user input directly into HTML
function Comment ({ userInput }) {
return < div dangerouslySetInnerHTML = {{ __html : userInput }} />;
}
为什么它容易受到攻击: 使用 dangerouslySetInnerHTML
处理不受信任的输入允许攻击者注入恶意脚本。
客户端模板注入当用户输入在模板中处理不当时发生,允许攻击者注入和执行模板或表达式。
易受攻击代码示例:
Copy import React from 'react' ;
import ejs from 'ejs' ;
function RenderTemplate ({ template , data }) {
const html = ejs .render (template , data);
return < div dangerouslySetInnerHTML = {{ __html : html }} />;
}
为什么它容易受到攻击: 如果 template
或 data
包含恶意内容,可能导致意外代码的执行。
客户端路径遍历这是一种漏洞,允许攻击者操纵客户端路径以执行意外操作,例如跨站请求伪造(CSRF)。与针对服务器文件系统的服务器端路径遍历不同,CSPT 侧重于利用客户端机制将合法的 API 请求重定向到恶意端点。
易受攻击代码示例:
一个 Next.js 应用程序允许用户上传和下载文件。下载功能在客户端实现,用户可以指定要下载的文件路径。
Copy // pages/download.js
import { useState } from 'react' ;
export default function DownloadPage () {
const [ filePath , setFilePath ] = useState ( '' );
const handleDownload = () => {
fetch ( `/api/files/ ${ filePath } ` )
.then (response => response .blob ())
.then (blob => {
const url = window . URL .createObjectURL (blob);
const a = document .createElement ( 'a' );
a .href = url;
a .download = filePath;
a .click ();
});
};
return (
< div >
< h1 >Download File</ h1 >
< input
type = "text"
value = {filePath}
onChange = {(e) => setFilePath ( e . target .value)}
placeholder = "Enter file path"
/>
< button onClick = {handleDownload}>Download</ button >
</ div >
);
}
攻击场景
攻击者的目标 :通过操纵 filePath
执行 CSRF 攻击以删除关键文件(例如,admin/config.json
)。
恶意输入 :攻击者构造一个带有操纵的 filePath
的 URL,例如 ../deleteFile/config.json
。
结果 API 调用 :客户端代码向 /api/files/../deleteFile/config.json
发出请求。
服务器的处理 :如果服务器不验证 filePath
,则会处理该请求,可能会删除或暴露敏感文件。
构造的链接 :攻击者向受害者发送一个链接或嵌入一个恶意脚本,触发带有操纵的 filePath
的下载请求。
结果 :受害者在不知情的情况下执行该操作,导致未经授权的文件访问或删除。
为什么它是脆弱的
缺乏输入验证 :客户端允许任意的 filePath
输入,从而启用路径遍历。
信任客户端输入 :服务器端 API 信任并处理未经过清理的 filePath
。
潜在的 API 操作 :如果 API 端点执行状态更改操作(例如,删除、修改文件),则可能通过 CSPT 被利用。
Next.js 中的服务器端
服务器端渲染 (SSR)
页面在每个请求时在服务器上渲染,确保用户接收到完全渲染的 HTML。在这种情况下,您应该创建自己的自定义服务器来处理请求。
用例:
SEO 优化,因为搜索引擎可以抓取完全渲染的页面。
实现:
Copy // pages/index.js
export async function getServerSideProps (context) {
const res = await fetch ( 'https://api.example.com/data' );
const data = await res .json ();
return { props : { data } };
}
function HomePage ({ data }) {
return < div >{ data .title}</ div >;
}
export default HomePage;
静态网站生成 (SSG)
页面在构建时预渲染,从而实现更快的加载时间和减少服务器负载。
用例:
实现:
Copy // pages/index.js
export async function getStaticProps () {
const res = await fetch ( 'https://api.example.com/data' );
const data = await res .json ();
return { props : { data } , revalidate : 60 }; // Revalidate every 60 seconds
}
function HomePage ({ data }) {
return < div >{ data .title}</ div >;
}
export default HomePage;
Serverless Functions (API Routes)
Next.js 允许创建作为无服务器函数的 API 端点。这些函数按需运行,无需专用服务器。
用例:
实现:
随着 Next.js 13 中 app
目录的引入,路由和 API 处理变得更加灵活和强大。这种现代方法与基于文件的路由系统紧密对齐,但引入了增强的功能,包括对服务器和客户端组件的支持。
基本路由处理程序
文件结构:
Copy my - nextjs - app /
├── app /
│ └── api /
│ └── hello /
│ └── route.js
├── package.json
└── ...
实现:
Copy // app/api/hello/route.js
export async function POST (request) {
return new Response ( JSON .stringify ({ message : 'Hello from App Router!' }) , {
status : 200 ,
headers : { 'Content-Type' : 'application/json' } ,
});
}
// Client-side fetch to access the API endpoint
fetch ( '/api/submit' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON .stringify ({ name : 'John Doe' }) ,
})
.then ((res) => res .json ())
.then ((data) => console .log (data));
解释:
位置: API 路由位于 app/api/
目录下。
文件命名: 每个 API 端点都有自己的文件夹,包含一个 route.js
或 route.ts
文件。
导出函数: 不再是单一的默认导出,而是导出特定的 HTTP 方法函数(例如,GET
、POST
)。
响应处理: 使用 Response
构造函数返回响应,从而更好地控制头部和状态码。
如何处理其他路径和方法:
处理特定的 HTTP 方法Next.js 13+ 允许您在同一个 route.js
或 route.ts
文件中定义特定 HTTP 方法的处理程序,从而促进更清晰和更有组织的代码。
示例:
Copy // app/api/users/[id]/route.js
export async function GET (request , { params }) {
const { id } = params;
// Fetch user data based on 'id'
return new Response ( JSON .stringify ({ userId : id , name : 'Jane Doe' }) , {
status : 200 ,
headers : { 'Content-Type' : 'application/json' } ,
});
}
export async function PUT (request , { params }) {
const { id } = params;
// Update user data based on 'id'
return new Response ( JSON .stringify ({ message : `User ${ id } updated.` }) , {
status : 200 ,
headers : { 'Content-Type' : 'application/json' } ,
});
}
export async function DELETE (request , { params }) {
const { id } = params;
// Delete user based on 'id'
return new Response ( JSON .stringify ({ message : `User ${ id } deleted.` }) , {
status : 200 ,
headers : { 'Content-Type' : 'application/json' } ,
});
}
解释:
多个导出: 每个 HTTP 方法(GET
,PUT
,DELETE
)都有其自己的导出函数。
参数: 第二个参数通过 params
提供对路由参数的访问。
增强响应: 更好地控制响应对象,实现精确的头部和状态码管理。
捕获所有和嵌套路由Next.js 13+ 支持高级路由功能,如捕获所有路由和嵌套 API 路由,允许更动态和可扩展的 API 结构。
捕获所有路由示例:
Copy // app/api/[...slug]/route.js
export async function GET (request , { params }) {
const { slug } = params;
// Handle dynamic nested routes
return new Response ( JSON .stringify ({ slug }) , {
status : 200 ,
headers : { 'Content-Type' : 'application/json' } ,
});
}
解释:
语法: [...]
表示一个捕获所有段的通用部分,捕获所有嵌套路径。
用法: 对于需要处理不同路由深度或动态段的 API 很有用。
嵌套路由示例:
Copy // app/api/posts/[postId]/comments/[commentId]/route.js
export async function GET (request , { params }) {
const { postId , commentId } = params;
// Fetch specific comment for a post
return new Response ( JSON .stringify ({ postId , commentId , comment : 'Great post!' }) , {
status : 200 ,
headers : { 'Content-Type' : 'application/json' } ,
});
}
解释:
深层嵌套: 允许层次化的API结构,反映资源关系。
参数访问: 通过params
对象轻松访问多个路由参数。
在Next.js 12及更早版本中处理API路由pages
目录中的API路由(Next.js 12及更早版本)
在Next.js 13引入app
目录并增强路由功能之前,API路由主要定义在pages
目录中。这种方法在Next.js 12及更早版本中仍然广泛使用和支持。
基本API路由
文件结构:
Copy goCopy codemy - nextjs - app /
├── pages /
│ └── api /
│ └── hello.js
├── package.json
└── ...
实施:
Copy javascriptCopy code // pages/api/hello.js
export default function handler (req , res) {
res .status ( 200 ) .json ({ message : 'Hello, World!' });
}
解释:
位置: API 路由位于 pages/api/
目录下。
导出: 使用 export default
来定义处理函数。
函数签名: 处理函数接收 req
(HTTP 请求)和 res
(HTTP 响应)对象。
路由: 文件名(hello.js
)映射到端点 /api/hello
。
动态 API 路由
文件结构:
Copy bashCopy codemy-nextjs-app/
├── pages/
│ └── api/
│ └── users/
│ └── [id].js
├── package.json
└── ...
实现:
Copy javascriptCopy code // pages/api/users/[id].js
export default function handler (req , res) {
const {
query: { id } ,
method ,
} = req;
switch (method) {
case 'GET' :
// Fetch user data based on 'id'
res .status ( 200 ) .json ({ userId : id , name : 'John Doe' });
break ;
case 'PUT' :
// Update user data based on 'id'
res .status ( 200 ) .json ({ message : `User ${ id } updated.` });
break ;
case 'DELETE' :
// Delete user based on 'id'
res .status ( 200 ) .json ({ message : `User ${ id } deleted.` });
break ;
default :
res .setHeader ( 'Allow' , [ 'GET' , 'PUT' , 'DELETE' ]);
res .status ( 405 ) .end ( `Method ${ method } Not Allowed` );
}
}
解释:
动态段: 方括号([id].js
)表示动态路由段。
访问参数: 使用 req.query.id
访问动态参数。
处理方法: 利用条件逻辑处理不同的 HTTP 方法(GET
、PUT
、DELETE
等)。
处理不同的 HTTP 方法
虽然基本的 API 路由示例在一个函数中处理所有 HTTP 方法,但您可以将代码结构化为明确处理每种方法,以提高清晰度和可维护性。
示例:
Copy javascriptCopy code // pages/api/posts.js
export default async function handler (req , res) {
const { method } = req;
switch (method) {
case 'GET' :
// Handle GET request
res .status ( 200 ) .json ({ message : 'Fetching posts.' });
break ;
case 'POST' :
// Handle POST request
res .status ( 201 ) .json ({ message : 'Post created.' });
break ;
default :
res .setHeader ( 'Allow' , [ 'GET' , 'POST' ]);
res .status ( 405 ) .end ( `Method ${ method } Not Allowed` );
}
}
最佳实践:
响应一致性: 确保一致的响应结构,以便于客户端处理。
CORS配置
控制哪些来源可以访问您的API路由,以减轻跨源资源共享(CORS)漏洞。
错误配置示例:
Copy // app/api/data/route.js
export async function GET (request) {
return new Response ( JSON .stringify ({ data : 'Public Data' }) , {
status : 200 ,
headers : {
'Access-Control-Allow-Origin' : '*' , // Allows any origin
'Access-Control-Allow-Methods' : 'GET, POST, PUT, DELETE' ,
} ,
});
}
注意,CORS 也可以在所有 API 路由中配置 ,位于 middleware.ts
文件内:
Copy // app/middleware.ts
import { NextResponse } from 'next/server' ;
import type { NextRequest } from 'next/server' ;
export function middleware (request : NextRequest ) {
const allowedOrigins = [ 'https://yourdomain.com' , 'https://sub.yourdomain.com' ];
const origin = request . headers .get ( 'Origin' );
const response = NextResponse .next ();
if ( allowedOrigins .includes (origin || '' )) {
response . headers .set ( 'Access-Control-Allow-Origin' , origin || '' );
response . headers .set ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, DELETE, OPTIONS' );
response . headers .set ( 'Access-Control-Allow-Headers' , 'Content-Type, Authorization' );
// If credentials are needed:
// response.headers.set('Access-Control-Allow-Credentials', 'true');
}
// Handle preflight requests
if ( request .method === 'OPTIONS' ) {
return new Response ( null , {
status : 204 ,
headers : response .headers ,
});
}
return response;
}
export const config = {
matcher : '/api/:path*' , // Apply to all API routes
};
问题:
Access-Control-Allow-Origin: '*'
: 允许任何网站访问API,可能导致恶意网站在没有限制的情况下与您的API交互。
广泛的方法允许: 允许所有方法可能使攻击者执行不必要的操作。
攻击者如何利用它:
攻击者可以构建恶意网站,向您的API发送请求,可能滥用诸如数据检索、数据操作或代表经过身份验证的用户触发不必要的操作等功能。
CORS - Misconfigurations & Bypass 客户端的服务器代码暴露
在客户端暴露和使用的代码中使用服务器使用的代码是很容易的 ,确保代码文件在客户端从未暴露的最佳方法是在文件开头使用此导入:
Copy import "server-only" ;
关键文件及其角色
middleware.ts
/ middleware.js
位置: 项目的根目录或 src/
中。
目的: 在请求处理之前,在服务器端无服务器函数中执行代码,允许进行身份验证、重定向或修改响应等任务。
执行流程:
响应修改: 可以更改响应或将控制权传递给下一个处理程序。
示例用例:
示例配置:
Copy // middleware.ts
import { NextResponse } from 'next/server' ;
import type { NextRequest } from 'next/server' ;
export function middleware (req : NextRequest ) {
const url = req . nextUrl .clone ();
if ( ! req . cookies .has ( 'token' )) {
url .pathname = '/login' ;
return NextResponse .redirect (url);
}
return NextResponse .next ();
}
export const config = {
matcher : [ '/protected/:path*' ] ,
};
next.config.js
位置: 项目的根目录。
目的: 配置 Next.js 的行为,启用或禁用功能,自定义 webpack 配置,设置环境变量,并配置多个安全功能。
关键安全配置:
安全头部安全头部通过指示浏览器如何处理内容来增强应用程序的安全性。它们有助于减轻各种攻击,如跨站脚本 (XSS)、点击劫持和 MIME 类型嗅探:
示例:
Copy // next.config.js
module . exports = {
async headers () {
return [
{
source : '/(.*)' , // Apply to all routes
headers : [
{
key : 'X-Frame-Options' ,
value : 'DENY' ,
} ,
{
key : 'Content-Security-Policy' ,
value : "default-src *; script-src 'self' 'unsafe-inline' 'unsafe-eval';" ,
} ,
{
key : 'X-Content-Type-Options' ,
value : 'nosniff' ,
} ,
{
key : 'Strict-Transport-Security' ,
value : 'max-age=63072000; includeSubDomains; preload' , // Enforces HTTPS
} ,
{
key : 'Referrer-Policy' ,
value : 'no-referrer' , // Completely hides referrer
} ,
// Additional headers...
] ,
} ,
];
} ,
};
图像优化设置Next.js 为性能优化图像,但错误配置可能导致安全漏洞,例如允许不受信任的来源注入恶意内容。
错误配置示例:
Copy // next.config.js
module . exports = {
images : {
domains : [ '*' ] , // Allows images from any domain
} ,
};
问题:
'*'
: 允许从任何外部来源加载图像,包括不受信任或恶意的域。攻击者可以托管包含恶意负载或误导用户内容的图像。
另一个问题可能是允许一个任何人都可以上传图像的域 (如 raw.githubusercontent.com
)
攻击者如何利用它:
通过从恶意来源注入图像,攻击者可以执行网络钓鱼攻击,显示误导性信息,或利用图像渲染库中的漏洞。
环境变量泄露安全地管理敏感信息,如 API 密钥和数据库凭据,而不将其暴露给客户端。
a. 泄露敏感变量
错误配置示例:
Copy // next.config.js
module . exports = {
env : {
SECRET_API_KEY : process . env . SECRET_API_KEY , // Exposed to the client
NEXT_PUBLIC_API_URL : process . env . NEXT_PUBLIC_API_URL , // Correctly prefixed for client
} ,
};
问题:
SECRET_API_KEY
: 如果没有 NEXT_PUBLIC_
前缀,Next.js 不会将变量暴露给客户端。然而,如果错误地添加了前缀(例如,NEXT_PUBLIC_SECRET_API_KEY
),它将在客户端可访问。
攻击者如何利用它:
如果敏感变量暴露给客户端,攻击者可以通过检查客户端代码或网络请求来检索它们,从而获得对 API、数据库或其他服务的未授权访问。
重定向管理应用程序内的 URL 重定向和重写,确保用户被适当地引导,而不会引入开放重定向漏洞。
a. 开放重定向漏洞
错误配置示例:
Copy // next.config.js
module . exports = {
async redirects () {
return [
{
source : '/redirect' ,
destination : (req) => req . query .url , // Dynamically redirects based on query parameter
permanent : false ,
} ,
];
} ,
};
问题:
动态目标: 允许用户指定任何 URL,从而启用开放重定向攻击。
信任用户输入: 在没有验证的情况下重定向到用户提供的 URL 可能导致网络钓鱼、恶意软件传播或凭证盗窃。
攻击者如何利用它:
攻击者可以构造看似来自您域的 URL,但将用户重定向到恶意网站。例如:
Copy https://yourdomain.com/redirect?url =https://malicious-site.com
用户信任原始域名可能会不知情地访问有害网站。
Webpack 配置自定义 Next.js 应用程序的 Webpack 配置,如果处理不当,可能会无意中引入安全漏洞。
a. 暴露敏感模块
错误配置示例:
Copy // next.config.js
module . exports = {
webpack : (config , { isServer }) => {
if ( ! isServer) {
config . resolve .alias[ '@sensitive' ] = path .join (__dirname , 'secret-folder' );
}
return config;
} ,
};
问题:
暴露敏感路径: 别名敏感目录并允许客户端访问可能会泄露机密信息。
捆绑秘密: 如果敏感文件被捆绑给客户端,其内容可以通过源映射或检查客户端代码访问。
攻击者如何利用它:
攻击者可以访问或重建应用程序的目录结构,可能找到并利用敏感文件或数据。
pages/_app.js
和 pages/_document.js
pages/_app.js
目的: 重写默认的 App 组件,允许全局状态、样式和布局组件。
用例:
示例:
Copy // pages/_app.js
import '../styles/globals.css' ;
function MyApp ({ Component , pageProps }) {
return < Component { ... pageProps} />;
}
export default MyApp;
pages/_document.js
目的: 重写默认文档,允许自定义 HTML 和 Body 标签。
使用案例:
示例:
Copy // pages/_document.js
import Document , { Html , Head , Main , NextScript } from 'next/document' ;
class MyDocument extends Document {
render () {
return (
< Html lang = "en" >
< Head >
{ /* Custom fonts or meta tags */ }
</ Head >
< body >
< Main />
< NextScript />
</ body >
</ Html >
);
}
}
export default MyDocument;
自定义服务器(可选)
目的: 虽然 Next.js 自带一个内置服务器,但您可以创建一个自定义服务器以满足高级用例,例如自定义路由或与现有后端服务集成。
注意: 使用自定义服务器可能会限制部署选项,特别是在像 Vercel 这样优化 Next.js 内置服务器的平台上。
示例:
Copy // server.js
const express = require ( 'express' );
const next = require ( 'next' );
const dev = process . env . NODE_ENV !== 'production' ;
const app = next ({ dev });
const handle = app .getRequestHandler ();
app .prepare () .then (() => {
const server = express ();
// Custom route
server .get ( '/a' , (req , res) => {
return app .render (req , res , '/a' );
});
// Default handler
server .all ( '*' , (req , res) => {
return handle (req , res);
});
server .listen ( 3000 , (err) => {
if (err) throw err;
console .log ( '> Ready on http://localhost:3000' );
});
});
额外的架构和安全考虑
环境变量和配置
目的: 在代码库之外管理敏感信息和配置设置。
最佳实践:
使用 .env
文件: 将 API 密钥等变量存储在 .env.local
中(不纳入版本控制)。
安全访问变量: 使用 process.env.VARIABLE_NAME
访问环境变量。
绝不要在客户端暴露秘密: 确保敏感变量仅在服务器端使用。
示例:
Copy // next.config.js
module . exports = {
env : {
API_KEY : process . env . API_KEY , // Accessible on both client and server
SECRET_KEY : process . env . SECRET_KEY , // Be cautious if accessible on the client
} ,
};
注意: 要将变量限制为仅服务器端使用,请将它们从 env
对象中省略或使用 NEXT_PUBLIC_
前缀以供客户端使用。
认证和授权
方法:
基于会话的认证: 使用 cookies 管理用户会话。
第三方提供者: 使用 next-auth
等库与 OAuth 提供者(例如,Google、GitHub)集成。
安全实践:
安全 Cookies: 设置 HttpOnly
、Secure
和 SameSite
属性。
示例:
Copy // pages/api/login.js
import { sign } from 'jsonwebtoken' ;
import { serialize } from 'cookie' ;
export default async function handler (req , res) {
const { username , password } = req .body;
// Validate user credentials
if (username === 'admin' && password === 'password' ) {
const token = sign ({ username } , process . env . JWT_SECRET , { expiresIn : '1h' });
res .setHeader ( 'Set-Cookie' , serialize ( 'auth' , token , { path : '/' , httpOnly : true , secure : true , sameSite : 'strict' }));
res .status ( 200 ) .json ({ message : 'Logged in' });
} else {
res .status ( 401 ) .json ({ error : 'Invalid credentials' });
}
}
性能优化
策略:
图像优化: 使用 Next.js 的 next/image
组件进行自动图像优化。
代码分割: 利用动态导入来分割代码并减少初始加载时间。
示例:
Copy // Dynamic Import with Code Splitting
import dynamic from 'next/dynamic' ;
const HeavyComponent = dynamic (() => import ( '../components/HeavyComponent' ) , {
loading : () => < p >Loading...</ p > ,
});