On this page
构建一个 Next.js 应用
Next.js 是一个用于构建服务器端渲染应用的流行框架。它基于 React 构建,并开箱即用提供了许多功能。
在本教程中,我们将使用 Deno 运行一个 简单的 Next.js 应用。该应用会展示一个恐龙列表。当你点击其中一个时,它会跳转到对应恐龙的详情页面。
你可以查看 GitHub 上的完整应用代码。
想跳过教程,立即部署完成后的应用吗?点击下面的按钮,即可立刻将完整的 Next.js 恐龙应用副本部署到 Deno Deploy。你将获得一个实时可用的应用,并且可以在学习过程中自由定制和修改!
使用 Deno 创建一个 Next.js 应用 Jump to heading
Next 提供了一个 CLI 工具,可以快速创建新的 Next.js 应用。在终端运行以下命令,使用 Deno 创建新的 Next.js 应用:
deno run -A npm:create-next-app@latest
当提示时,选择默认选项以创建带有 TypeScript 的新 Next.js 应用。
Next.js 的一些依赖仍然依赖 Object.prototype.__proto__,并且需要 CommonJS 模块支持。为让 Deno 兼容 Next.js,请更新你的 deno.json 文件,使用以下配置:
{
"unstable": [
"detect-cjs",
"node-globals",
"unsafe-proto",
"sloppy-imports"
]
}
Next.js 会在本地构建和 Deploy 构建期间读取 next.config.*。使用 Deno 时,建议使用名为 next.config.mjs 的 ESM 配置文件。当前版本的 Next.js 也支持使用相同 export default 结构的 next.config.ts:
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
如果你使用生成了带有 module.exports 的 next.config.js 的脚手架开始项目,Deno 的 detect-cjs 选项通常可以让该 CommonJS 配置正常工作。如果仍然看到 module is not defined,或者某个工具将配置作为 ES 模块加载,请将其重命名为 next.config.mjs,并按照上面的示例使用 export default。
现在安装 package.json 中的依赖:
deno install --allow-scripts
现在可以运行新的 Next.js 应用:
deno task dev
这会使用 Deno 启动 Next.js 开发服务器。deno task dev 命令会在启动时带上必要的标志,以启用对 CommonJS 的 Next.js 开发服务器支持。
访问 http://localhost:3000 在浏览器中查看应用。
添加后台服务 Jump to heading
下一步是添加后台 API。我们将创建一个非常简单的 API,用于返回关于恐龙的信息。
我们会使用 Next.js 内置的 API 路由处理 来设置恐龙 API。Next.js 使用基于文件系统的路由,文件夹结构直接定义路由。
我们将定义三个路由:第一个 /api 路由返回字符串 欢迎来到恐龙 API,然后 /api/dinosaurs 返回所有恐龙的数据,最后 /api/dinosaurs/[dinosaur] 根据 URL 中的名称返回特定恐龙。
/api/ Jump to heading
在新项目的 src/app 文件夹中创建一个 api 文件夹,在该文件夹中创建 route.ts 文件,用于处理 /api 路由。
将以下代码复制粘贴到 api/route.ts 文件中:
export function GET() {
return Response.json("welcome to the dinosaur API");
}
此代码定义了一个简单的路由处理器,返回包含字符串 welcome to the dinosaur API 的 JSON 响应。
/api/data.json Jump to heading
在 api 文件夹中,创建一个 data.json 文件,并填入硬编码的恐龙数据。将
这个 JSON 文件
复制粘贴到 data.json 文件中。
/api/dinosaurs Jump to heading
在 api 文件夹中,创建一个名为 dinosaurs 的文件夹,并在其中创建一个 route.ts 文件来处理 /api/dinosaurs 请求。该路由将读取 data.json 文件,并返回所有恐龙的 JSON 数据:
import data from "./data.json" with { type: "json" };
export function GET() {
return Response.json(data);
}
/api/dinosaurs/[dinosaur] Jump to heading
对于最后一个路由 /api/dinosaurs/[dinosaur],在 dinosaurs 目录中创建一个 [dinosaur] 文件夹。在其中创建 route.ts 文件。该文件将读取 data.json,根据 URL 中的名称查找对应恐龙并以 JSON 返回:
import data from "../../data.json" with { type: "json" };
type RouteParams = { params: Promise<{ dinosaur: string }> };
export const GET = async (_request: Request, { params }: RouteParams) => {
const { dinosaur } = await params;
if (!dinosaur) {
return Response.json("未提供恐龙名称。");
}
const dinosaurData = data.find((item) =>
item.name.toLowerCase() === dinosaur.toLowerCase()
);
return Response.json(dinosaurData ? dinosaurData : "未找到该恐龙。");
};
现在,如果你运行应用并访问
http://localhost:3000/api/dinosaurs/brachiosaurus,应该就能看到关于腕龙的详细信息。
构建前端 Jump to heading
现在我们已经设置了后台 API,接下来构建前端页面以展示恐龙数据。
定义恐龙类型 Jump to heading
首先,我们添加一个类型定义,用于描述恐龙数据结构。在 app 目录中创建 types.ts 文件,并添加以下代码:
export type Dino = { name: string; description: string };
更新首页 Jump to heading
修改 app 目录中的 page.tsx 文件,从我们的 API 获取恐龙数据,并以链接列表的形式显示。
要在 Next.js 中执行客户端代码,我们需要在文件顶部使用 "use client" 指令。然后,我们将导入此页面所需的模块,并导出用于渲染页面的默认函数:
"use client";
import { useEffect, useState } from "react";
import { Dino } from "./types";
import Link from "next/link";
export default function Home() {
}
在 Home 函数体内,定义一个状态变量用于存储恐龙数据,并在组件挂载时使用 useEffect 钩子从 API 拉取数据:
const [dinosaurs, setDinosaurs] = useState<Dino[]>([]);
useEffect(() => {
(async () => {
const response = await fetch(`/api/dinosaurs`);
const allDinosaurs = await response.json() as Dino[];
setDinosaurs(allDinosaurs);
})();
}, []);
接着,在 Home 函数体内返回一个链接列表:每个链接指向对应的恐龙页面:
return (
<main id="content">
<h1>欢迎来到恐龙应用</h1>
<p>点击下面的恐龙了解更多信息。</p>
<ul>
{dinosaurs.map((dinosaur: Dino) => {
return (
<li key={dinosaur.name}>
<Link href={`/${dinosaur.name.toLowerCase()}`}>
{dinosaur.name}
</Link>
</li>
);
})}
</ul>
</main>
);
创建恐龙详情页面 Jump to heading
在 app 目录下创建名为 [dinosaur] 的文件夹,并在其中创建 page.tsx 文件。该文件将从 API 获取特定恐龙详情并进行渲染。
和首页类似,我们添加客户端代码的导入,并导出默认函数,同时将入参进行类型标注:
"use client";
import { useEffect, useState } from "react";
import { Dino } from "../types";
import Link from "next/link";
type RouteParams = { params: Promise<{ dinosaur: string }> };
export default function Dinosaur({ params }: RouteParams) {
}
在 Dinosaur 函数中,获取 URL 中选定的恐龙名称,定义状态变量用于存储恐龙信息,并在挂载时通过 useEffect 钩子从 API 获取数据:
const selectedDinosaur = params.then((params) => params.dinosaur);
const [dinosaur, setDino] = useState<Dino>({ name: "", description: "" });
useEffect(() => {
(async () => {
const resp = await fetch(`/api/dinosaurs/${await selectedDinosaur}`);
const dino = await resp.json() as Dino;
setDino(dino);
})();
}, []);
最后,在组件中返回用于展示恐龙名称及描述的元素:
return (
<main id="content">
<h1>{dinosaur.name}</h1>
<p>{dinosaur.description}</p>
<Link href="/">🠠 返回所有恐龙</Link>
</main>
);
添加样式 Jump to heading
为应用添加基础样式以使界面更美观。更新 app/globals.css 文件,并使用
此文件中的样式。
运行应用 Jump to heading
现在,你可以用 deno run dev 启动应用,然后在浏览器访问 http://localhost:3000 查看恐龙列表。点击恐龙即可看到更详细的信息!
部署应用 Jump to heading
既然你的 Next.js 应用已经运行起来,你可以使用 Deno DeployEA 将其部署到线上。
如果在 Deno 上运行 next build 时出现
Cannot read properties of undefined (reading 'bold'),问题出在
Next.js 的构建时 lint/类型检查流程中。临时的解决方法是将
typescript.ignoreBuildErrors 和 eslint.ignoreDuringBuilds 添加到
next.config.mjs 中,然后在 CI 中分别运行类型检查和 lint。
如果客户端组件使用了仅限浏览器环境的 hooks 或 API,Next.js 仍可能会在预渲染或 SSR 期间尝试在服务器上渲染它。请将这部分代码延迟到组件挂载之后执行,例如放在 useEffect 中。如果你需要使用 next/dynamic 和 { ssr: false } 禁用 SSR,请将动态导入放在客户端组件包装器中;服务器组件不能直接使用 { ssr: false }。
仅将路由标记为动态是不够的,因为动态路由仍会在服务器上渲染,而浏览器全局变量在服务器上是未定义的。
为了获得最佳体验,你可以直接从 GitHub 部署应用,这样就能设置自动部署。创建一个 GitHub 仓库并将应用推送到其中。
创建新的 GitHub 仓库,然后初始化并推送应用:
git init -b main
git remote add origin https://github.com/<your_github_username>/<your_repo_name>.git
git add .
git commit -am 'my next app'
git push -u origin main
当你的应用部署到 GitHub 后,你就可以 部署到 Deno DeployEA。
想了解部署详情,请查看 Deno Deploy 教程。
🦕 现在你可以使用 Deno 构建和运行 Next.js 应用了!如果你想在此基础上继续扩展应用,可以考虑添加数据库来替换你的 data.json 文件,或者考虑编写一些测试来让你的应用更可靠,并为生产环境做好准备。
Last updated on