首页 > web前端 > js教程 > 正文

使用 DeployHQ 将 React Watchlist Tracker 应用程序部署到生产环境

Susan Sarandon
发布: 2024-11-02 16:28:29
原创
473 人浏览过

在今天的教程中,我们将学习如何自行托管和设置我们的服务器,这将使我们能够在线部署任何 Web 应用程序。有几种在线部署应用程序的方法。两种策略涉及使用 VPS、虚拟专用服务器和共享托管平台(如 Vercel、Netlify、WordPress、GoDaddy 等)

VPS 与托管平台相比如何?

VPS 是一种虚拟机,它在与其他用户物理共享的服务器上提供专用服务器资源。它实际上是网站和应用程序的中间层托管,与共享托管相比,提供更多的控制和定制。一些 VPS 托管平台的示例包括 Hetzner、Akamai、Vultr、Cloudcone。

另一方面,使用托管托管平台托管网站会更容易。此类平台提供用于构建和部署 Web 应用程序的工具、工作流程和基础设施。这些平台自行执行负载平衡和缓存。它们非常适合任何希望快速构建和部署 Web 应用程序而无需进一步配置的开发人员。

一如既往,使用每种策略都有优点和缺点。最显着的区别之一是,使用 VPS 时,您可以获得完全的自定义,因为您可以控制整个服务器及其附带的所有内容。这意味着设置开发环境、防火墙规则、托管等。这种定制增加了复杂性,并且您需要更多的技术支持,因为所有事情都是您自己做的。您应该知道托管平台对初学者非常友好,因为大多数工具都是预先设置的,并且您可以获得支持和文档。因为它已经设置和管理,所以您将无法获得从 VPS 获得的高水平定制。

此外,您还必须考虑价格差异。大多数 VPS 都是付费的,但您可以完全自定义您的服务器,使其轻量级或强大到您需要的程度。在性能方面,这使其优于任何托管平台。后者确实有免费计划,因此您需要查看差异。一般消费者会想要托管平台,因为它们是免费的。但是,如果您需要更多功能并希望在一个应用程序中托管高级应用程序,那么 VPS 是您的最佳选择。

监视列表跟踪器应用程序的功能概述

我们的观看列表跟踪器应用程序是一个简单但功能强大的全栈 CRUD 应用程序,将用于跟踪电影观看列表。该应用程序还将使用户能够轻松添加他们想要观看的电影或系列,更新电影标题或评级,以及删除他们已经观看或不再希望跟踪的电影。该应用程序为用户提供了一个简单的界面来组织和追看感兴趣的电影,使其成为那些想要在关注列表上保持领先的电影爱好者的工具。

您可以看到该应用程序如下所示:

主页

Deploying a React Watchlist Tracker App to Production Using DeployHQ
电影/系列商品页面

Deploying a React Watchlist Tracker App to Production Using DeployHQ

添加新项目页面

Deploying a React Watchlist Tracker App to Production Using DeployHQ

设置 Vite Watchlist Tracker 应用程序

先决条件

在我们开始构建应用程序之前,必须设置并运行您的开发环境。确保您的计算机上安装了以下软件:

  • VS Code - 代码编辑器
  • Node.js 和 npm - 跨平台、开源 JavaScript 运行时环境
  • GIT - 分布式版本控制系统
  • Bun - JavaScript 运行时、包管理器、测试运行器和捆绑器
  • Vite - 现代 JavaScript 构建工具
  • SQLite - 便携式数据库
  • PM2 - JavaScript 运行时 Node.js 的进程管理器。
  • API 测试工具,如 Postman、Thunder Client 或替代品

VS Code 有一个优秀的免费 SQLite Viewer 扩展,除了使用命令行之外,它也很有帮助。它有助于快速查看数据库内的数据。

技术栈

我们的 Watchlist Tracker 应用程序是使用非常现代且具有前瞻性的技术堆栈构建的,可提供出色的开发体验。我选择使用 Bun、Hono、Vite、TanStack、Hetzner 和 DeployHQ 等工具,因为它们都为开发人员提供了现代的构建体验。

让我们看一下我们将在这个项目中使用的技术:

后端

  • Bun:Bun 是一个高速 JavaScript 运行环境,很像 Node.js;然而,开发者体验和整体速度更快。 Bun 允许开发人员快速执行代码,并为开发人员期望的许多功能提供无数内置支持,例如测试运行程序和捆绑程序。
  • Hono:Hono 是一个轻量级 Web 框架,可以与 Bun 无缝协作,这就是为什么当您想要构建快速 API 时它是一个很好的匹配。 Hono 的简单方法意味着开发人员可以创建不需要太多时间和高复杂性的应用程序逻辑。
  • Prisma ORM:Prisma ORM 是一个强大且现代的对象关系映射工具,可以简化数据库管理。它是类型安全的,因此与数据库交互可确保数据完整性并减少运行时的错误。
  • SQLite:SQLite 是一种轻量级、基于文件的数据库,在中小型项目中表现出色。它的设置速度很快,并且在处理需要速度和复杂性较低的项目时效果最好。

前端

  • Vite:Vite 是下一代前端构建工具,可用于创建不同类型的 JavaScript 应用程序。它专为提高速度而设计,可用于在 React、Vue 和其他 JavaScript 框架中构建项目。
  • Tailwind CSS:Tailwind CSS 是一个实用程序优先的 CSS 框架,允许开发人员使用低级实用程序类快速构建用户界面。它允许进行大量自定义,并且非常擅长创建响应式网站。
  • TanStack Router:TanStack Router 是一个针对 React 应用程序的灵活而强大的路由解决方案。它支持嵌套路由和页面转换等高级功能,使其成为在单页面应用程序中设置路由的绝佳现代选项。

托管和部署

  • Hetzner:Hetzner 是一家受欢迎且可靠的云托管提供商,以其良好的性能和实惠的选择而闻名。借助 Hetzner 的基础架构,您可以确保您的应用程序在被世界各地的用户访问时保持可访问性,同时保持高性能。
  • DeployHQ:DeployHQ是一个可以简化部署过程的平台。它允许开发人员将他们的网站从 Git、SVN 和 Mercurial 存储库部署到他们自己的服务器。此过程保证您的应用程序代码在生产中始终是最新的,并且有可靠的自动化部署过程,这更有利于安全性。

构建监视列表跟踪器应用程序

好吧,让我们开始构建我们的应用程序!本节将分为两部分:首先,我们将制作后端,然后我们将创建前端。

构建后端

请在您的计算机上为名为 watchlist-tracker-app 的项目创建一个新文件夹,然后通过 cd 进入该文件夹。现在,使用此处显示的以下命令为后端创建一个新的 Bun 项目:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们的项目现在应该已经建立了。我们只需安装依赖项、进行配置并编写一些服务器代码。在代码编辑器中打开项目。

现在使用以下命令安装我们服务器的依赖项:

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们添加了 Bun 作为运行时环境,Hono 作为我们的 API 服务器,Prisma 作为我们的数据库 ORM。

现在让我们使用以下命令设置 Prisma ORM 和 SQLite:

npx prisma init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

Prisma 现在应该配置为在我们的服务器中工作,因此在下一步中,我们将配置我们的数据库架构。因此,将 prisma/schema.prisma 中的所有代码替换为以下代码:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model WatchlistItem {
  id          Int      @id @default(autoincrement())
  name        String
  image       String
  rating      Float
  description String
  releaseDate DateTime
  genre       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们的数据库架构已设置,并且已连接到我们的 SQLite 数据库文件。

现在运行此迁移脚本来创建 SQLite 数据库:

npx prisma migrate dev --name init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

太好了,现在 Prisma 迁移已经完成,我们可以处理我们的 API 文件了。

现在让我们创建我们的主 API 文件,它将使用 Hono。转到 src/server.ts 内的服务器文件并将以下代码添加到文件中:

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { PrismaClient } from '@prisma/client';

const app = new Hono();

app.use(cors());

const prisma = new PrismaClient();

app.get('/watchlist', async (c) => {
  const items = await prisma.watchlistItem.findMany();
  return c.json(items);
});

app.get('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const item = await prisma.watchlistItem.findUnique({
    where: { id: Number(id) },
  });
  return item ? c.json(item) : c.json({ error: 'Item not found' }, 404);
});

app.post('/watchlist', async (c) => {
  const data = await c.req.json();
  const newItem = await prisma.watchlistItem.create({ data });
  return c.json(newItem);
});

app.put('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const data = await c.req.json();
  const updatedItem = await prisma.watchlistItem.update({
    where: { id: Number(id) },
    data,
  });
  return c.json(updatedItem);
});

app.delete('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  await prisma.watchlistItem.delete({ where: { id: Number(id) } });
  return c.json({ success: true });
});

Bun.serve({
  fetch: app.fetch,
  port: 8000,
});

console.log('Server is running on http://localhost:8000');

export default app;
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

通过此文件,我们的服务器将使用 Bun 和 Hono 并在端口 8000 上运行。我们还拥有监视列表跟踪器的所有 CRUD(创建、读取、更新、删除)端点。我们所有的数据都保存在 SQLite 数据库中。

剩下的就是为我们的服务器创建运行和构建脚本。然后,我们可以测试端点以确保它们按预期工作。将这些运行脚本添加到我们的 package.json 文件中:

"scripts": {
    "start": "bun run src/server.ts",
    "build": "bun build src/server.ts --outdir ./dist --target node"
},
登录后复制
登录后复制
登录后复制

好的,现在如果您运行命令 Bun run start,您应该在终端中看到以下内容,确认服务器正在运行:

Server is running on http://localhost:8000
登录后复制
登录后复制

如果运行命令bun run build,它应该创建一个可供生产使用的 dist 文件夹。当我们在 Hetzner 或任何在线服务器上部署应用程序时,我们将需要这个。

好吧,让我们快速测试我们的后端端点以确保它们按预期工作。然后,我们就可以开始开发前端了。我们有五个端点要测试:两个 GET、一个 POST、一个 PUT 和一个 DELETE。我将使用 Postman 来测试 API。

监视列表应用 API POST 端点

方法:POST
端点:http://localhost:8000/watchlist

这是我们的 POST 端点,用于将包含电影/连续剧数据的 JSON 对象发送到我们的数据库。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

监视列表应用 API 获取所有端点

方法:GET
端点:http://localhost:8000/watchlist

这是我们的主要 GET 端点,它将返回前端将获取的对象数组。如果我们尚未将任何数据发布到数据库,它会返回一个包含我们数据的对象数组或一个空数组。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

Watchlist App API GET By ID Endpoint

方法:GET
端点:http://localhost:8000/watchlist/3

这是我们通过 ID 获取项目的 GET 端点。它仅返回该对象,如果该项目不存在,则显示错误。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

监视列表应用 API PUT 端点

方法:PUT
端点:http://localhost:8000/watchlist/3

这是我们使用 ID 更新项目的 PUT 端点。它仅返回该对象,如果该项目不存在,则显示错误。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

监视列表应用 API 删除端点

方法:删除
端点:http://localhost:8000/watchlist/3

这是我们的 DELETE 端点,用于使用 ID 删除项目。它返回一个成功对象,如果该项目不存在则显示错误。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

也就是说,我们的 API 已启动并正在运行。我们现在可以开始编写前端代码了。

构建前端

确保您位于 watchlist-tracker-app 的根文件夹中,然后运行下面的这些脚本以使用 Vite 创建一个 React 项目,该项目是为 TypeScript 设置的,并包含我们所有的包和依赖项:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

这个脚本基本上使用 Bun 运行时环境来安装和设置我们的项目。所有必需的文件和文件夹都已创建,因此我们只需添加代码即可。我们已经将 Vite 项目设置为使用 Tailwind CSS 进行样式设置,并使用 TanStack Router 进行页面路由,使用 axios 获取数据,使用 dayjs 在表单中进行日期转换。

感谢这个构建脚本,我们的工作现在明显简单了,所以让我们开始将代码添加到我们的文件中。首先是一些配置文件。将 tailwind.config.js 文件中的所有代码替换为以下代码:

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

这个文件非常有解释性。我们需要此文件,以便 Tailwind CSS 在我们的项目中正常工作。

现在用此代码替换 src/index.css 文件中的所有代码,我们需要添加 Tailwind 指令,以便我们可以在 CSS 文件中使用它们:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

添加这些指令后,我们可以在所有 CSS 文件中访问 Tailwind CSS 样式。接下来删除 App.css 文件中的所有 CSS 代码,因为我们不再需要它。

好吧,现在是最终的配置文件,然后我们就可以处理我们的页面和组件了。

将此代码添加到根文件夹中的 api.ts 文件中:

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

此文件导出我们的前端需要在后端连接到的端点。请记住,我们的后端 API 位于 http://localhost:8000。

好的,让我们用这个新代码替换 App.tsx 文件中的所有代码:

npx prisma init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

这是我们应用程序的主要入口点组件,该组件保存我们所有页面的路由。

接下来,我们将处理主要组件和页面。我们有三个组件文件和三页文件。从组件开始,将此代码添加到我们的文件中的 elements/AddItemForm.tsx:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model WatchlistItem {
  id          Int      @id @default(autoincrement())
  name        String
  image       String
  rating      Float
  description String
  releaseDate DateTime
  genre       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

该组件用于将项目添加到我们的数据库中。用户将使用此表单组件向后端发送 POST 请求。

现在让我们添加 Components/FormField.tsx 的代码:

npx prisma migrate dev --name init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

这是我们表单的可重用表单字段组件。它使我们的代码保持干燥,因为我们可以对多个字段使用相同的组件,这意味着我们的代码库更小。

最后让我们添加 Components/Header.tsx 的代码:

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { PrismaClient } from '@prisma/client';

const app = new Hono();

app.use(cors());

const prisma = new PrismaClient();

app.get('/watchlist', async (c) => {
  const items = await prisma.watchlistItem.findMany();
  return c.json(items);
});

app.get('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const item = await prisma.watchlistItem.findUnique({
    where: { id: Number(id) },
  });
  return item ? c.json(item) : c.json({ error: 'Item not found' }, 404);
});

app.post('/watchlist', async (c) => {
  const data = await c.req.json();
  const newItem = await prisma.watchlistItem.create({ data });
  return c.json(newItem);
});

app.put('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const data = await c.req.json();
  const updatedItem = await prisma.watchlistItem.update({
    where: { id: Number(id) },
    data,
  });
  return c.json(updatedItem);
});

app.delete('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  await prisma.watchlistItem.delete({ where: { id: Number(id) } });
  return c.json({ success: true });
});

Bun.serve({
  fetch: app.fetch,
  port: 8000,
});

console.log('Server is running on http://localhost:8000');

export default app;
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

由于这个标题组件,每个页面都有一个带有主导航的标题。

我们只剩下三页了,然后我们的应用程序就完成了。因此,将以下代码添加到pages/AddItem.tsx:

"scripts": {
    "start": "bun run src/server.ts",
    "build": "bun build src/server.ts --outdir ./dist --target node"
},
登录后复制
登录后复制
登录后复制

这本质上是向我们的数据库添加项目的页面,其中包含表单组件。

是的,接下来让我们添加pages/Home.tsx的代码:

Server is running on http://localhost:8000
登录后复制
登录后复制

正如您所想象的,这将是我们的主页,它向后端发送 GET 请求,然后后端检索数据库中所有项目的对象数组。

最后添加pages/ItemDetail.tsx的代码:

bun create vite client --template react-ts
cd client
bunx tailwindcss init -p
bun install -D tailwindcss postcss autoprefixer tailwindcss -p
bun install @tanstack/react-router axios dayjs
cd src
mkdir components pages
touch api.ts
touch components/{AddItemForm,FormField,Header}.tsx pages/{AddItem,Home,ItemDetail}.tsx
cd ..
登录后复制

此页面按 ID 显示各个项目页面。还有一个用于编辑和删除数据库中的项目的表单。

就是这样。我们的应用程序已准备好使用。确保后端服务器和客户端服务器都在运行。您应该在浏览器中看到该应用程序正在运行:http://localhost:5173/.

在其文件夹中使用以下命令运行两台服务器:

module.exports = {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};
登录后复制

好的,干得好。我们的申请已完成!现在让我们将其部署到 GitHub!

将应用程序部署到 GitHub

将我们的应用程序部署到 GitHub 非常简单。首先将 .gitignore 文件放入我们的 watchlist-tracker-app 的根文件夹中。您只需从后端或客户端文件夹复制并粘贴 .gitignore 文件即可。现在转到您的 GitHub(如果没有帐户,请创建一个帐户)并为您的 watchlist-tracker-app 创建一个存储库。

在 watchlist-tracker-app 的根文件夹内使用命令行上传您的代码库。请参阅此示例代码并将其改编为您自己的存储库:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

最后一点需要注意。当我们将代码上传到 Hetzner 时,我们将无法再通过 localhost 访问后端,因此我们必须更新 API 路由。有一个变量叫做 const API_URL = 'http://localhost:8000';在它们顶部的两个文件中。这些文件是 api.ts 和 Components/AddItemForm.tsx。将变量 API URL 替换为下面的 URL,然后将您的代码库重新上传到 GitHub。

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

这就是全部内容了。您的代码库现在应该在 GitHub 上在线,以便我们现在可以将其部署到 Hetzner。

在 Hetzner 上创建帐户

我们的监视列表跟踪器应用程序完成后,现在可以将我们的应用程序部署到 Hetzner VPS 平台了。 Hetzner 是一个付费平台,但它提供的多功能性是无与伦比的。最便宜的计划约为每月 4.51 欧元/4.88 美元/3.76 英镑。拥有自托管在线服务器是非常值得的,因为作为开发人员,您可以使用它来学习、练习、生产部署等等。您可以随时取消服务器订阅并在需要时取回。

大多数VPS本质上都是一样的,因为它们都是可以运行不同操作系统的服务器,比如Linux。每个 VPS 提供商都有不同的界面和设置,但基本结构完全相同。通过学习如何在 Hetzner 上自行托管应用程序,您可以轻松地重复使用这些相同的技能和知识,在不同的 VPS 平台上部署应用程序。

VPS 的一些用例包括:

  • 虚拟主机
  • 应用程序托管
  • 游戏服务器
  • VPN 或代理服务器
  • 电子邮件服务器
  • 数据库托管
  • 自动化和开发任务
  • 媒体流和文件托管
  • 数据分析和机器学习
  • 学习和实验

您可以在这里查看 Hetzner 的当前定价:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

转到 Hetzner 网站,然后单击页面中间的红色“注册”按钮。或者,您可以单击右上角的登录按钮。您应该会看到一个菜单,其中包含 Cloud、Robot、konsoleH 和 DNS 选项。点击其中任意一个即可进入登录和注册表单页面。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

Deploying a React Watchlist Tracker App to Production Using DeployHQ

现在,您应该看到登录和注册表单页面。单击注册按钮创建帐户并完成注册过程。您可能需要使用 PayPal 帐户或准备好护照来通过验证阶段。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

您现在应该能够登录您的帐户并创建和购买服务器。选择您附近的位置,然后选择 Ubuntu 作为映像。请参阅此示例以供参考。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

在“类型”下,选择共享 vCPU,然后选择服务器的配置。我们正在部署一个仅需要少量资源的简单应用程序。如果您愿意,可以随意获得更好的服务器 - 这取决于您。专用 vCPU 性能更好,但成本更高。您还可以选择 x86 (Intel/AMD) 和 Arm64 (Ampere) 处理器。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

默认配置应该可以接受。请参阅下面的示例。不过,出于安全原因,添加 SSH 密钥很重要。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

SSH 密钥提供了比传统密码更安全的服务器身份验证方式。密钥需要采用 OpenSSH 格式,以确保服务器的高度安全。根据您的操作系统,您可以在 Google 中搜索“在 Mac 上生成 SSH 密钥”或“在 Windows 上生成 SSH 密钥”。我将为您提供在 Mac 上生成 SSH 密钥的快速指南。

首先打开您的终端应用程序,然后键入以下命令,将“your_email@example.com”替换为您的实际电子邮件地址:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

-b 4096 部分确保密钥为 4096 位,以提高安全性。接下来,在出现提示后保存密钥。您可以接受默认位置或选择自定义位置:

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

设置密码是可选的,您可以跳过此步骤。现在,通过运行以下命令将 SSH 密钥加载到 SSH 代理中:

首先,启动代理:

npx prisma init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

然后,添加 SSH 密钥:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model WatchlistItem {
  id          Int      @id @default(autoincrement())
  name        String
  image       String
  rating      Float
  description String
  releaseDate DateTime
  genre       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

要将 SSH 公钥复制到剪贴板,请运行:

npx prisma migrate dev --name init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

这会复制公钥,以便您可以将其添加到 Hetzner 或任何其他需要 SSH 密钥的服务。将您的 SSH 密钥粘贴到此表单框中并添加它。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

您无需担心卷、置放组、标签或云配置,因为它们超出了本项目的范围。备份可能会有所帮助,但会增加成本,因此对于该项目来说它们是可选的。我们稍后会做防火墙,所以你现在不用担心。为您的服务器选择一个名称,然后使用您当前的设置创建并购买服务器,您的 Hetzner 帐户就可以使用了。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

好的,很好。我们现在在 Hetzner 上有一个帐户。在下一节中,我们将设置防火墙、配置 Linux 操作系统并使应用程序上线。

将 Watchlist Tracker 应用程序部署到 Hetzner

创建 Hetzner 防火墙

在通过 SSH 进入 Linux 操作系统之前,我们首先设置防火墙规则。我们需要打开端口 22 以便可以使用 SSH,并且需要打开端口 80,因为端口 80 是 TCP 端口,是使用 HTTP(超文本传输​​协议)的 Web 服务器的默认网络端口。 它用于在 Web 浏览器和服务器之间进行通信,传送和接收 Web 内容。这就是我们如何让我们的应用程序在线运行。

导航到主菜单中的防火墙,然后使用如下所示的入站规则创建防火墙:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

Deploying a React Watchlist Tracker App to Production Using DeployHQ

现在,随着我们的防火墙工作,我们可以设置 Linux 环境并部署我们的应用程序,所以接下来我们就这样做。

设置我们的Linux环境

连接到我们的远程 Hetzner 服务器应该非常简单,因为我们已经创建了用于登录的 SSH 密钥。我们可以使用终端甚至像 VS Code 这样的代码编辑器进行连接,这将使我们能够查看和浏览我们的无需使用命令行即​​可更轻松地访问文件。如果您想使用 Visual Studio Code Remote - SSH 扩展,则可以这样做。对于这个项目,我们将坚持使用终端。

打开终端并输入此 SSH 命令以登录远程服务器。将地址 11.11.111.111 替换为您的实际 Hetzner IP 地址,您可以在服务器部分找到该地址:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

您现在应该登录到您的服务器,该服务器显示欢迎屏幕和其他私人信息,例如您的 IP 地址。我只是在这里显示屏幕的欢迎部分:

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

好的,太好了。现在,我们终于可以开始运行一些命令,这些命令将在我们的应用程序上线之前设置我们的开发环境。顺便说一句,如果您想退出并关闭与服务器的 SSH 连接,您可以键入 exit 并单击 Enter 按钮,也可以使用键盘快捷键 Ctrl D。

我们需要做的第一件事是更新和升级 Linux 系统上的软件包。我们可以使用一个命令来完成此操作,因此请在终端中输入此命令并按 Enter 键:

npx prisma init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

现在,我们需要安装其余的开发包和依赖项。首先是 Node.js 和 npm,因此使用以下命令安装它们:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model WatchlistItem {
  id          Int      @id @default(autoincrement())
  name        String
  image       String
  rating      Float
  description String
  releaseDate DateTime
  genre       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

接下来是 Nginx,我们需要将其用作反向代理。反向代理是位于客户端和后端服务器之间的服务器,将客户端请求转发到适当的后端服务器,然后将服务器的响应返回给客户端。它充当中介,管理和路由传入请求以提高性能、安全性和可扩展性。因此使用以下命令安装它:

npx prisma migrate dev --name init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们将需要 Git,为此我们需要从 GitHub 提取代码并将其上传到我们的远程服务器。因此使用以下命令安装它:

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { PrismaClient } from '@prisma/client';

const app = new Hono();

app.use(cors());

const prisma = new PrismaClient();

app.get('/watchlist', async (c) => {
  const items = await prisma.watchlistItem.findMany();
  return c.json(items);
});

app.get('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const item = await prisma.watchlistItem.findUnique({
    where: { id: Number(id) },
  });
  return item ? c.json(item) : c.json({ error: 'Item not found' }, 404);
});

app.post('/watchlist', async (c) => {
  const data = await c.req.json();
  const newItem = await prisma.watchlistItem.create({ data });
  return c.json(newItem);
});

app.put('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const data = await c.req.json();
  const updatedItem = await prisma.watchlistItem.update({
    where: { id: Number(id) },
    data,
  });
  return c.json(updatedItem);
});

app.delete('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  await prisma.watchlistItem.delete({ where: { id: Number(id) } });
  return c.json({ success: true });
});

Bun.serve({
  fetch: app.fetch,
  port: 8000,
});

console.log('Server is running on http://localhost:8000');

export default app;
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

Bun 运行时将有助于运行我们的应用程序。首先,在安装Bun之前,我们必须按照要求安装解压包。之后我们就可以安装Bun了。这些是我们需要的命令:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

要在同一台 Hetzner 服务器上运行 React 前端和后端服务器,我们需要同时管理这两个服务。这通常涉及设置像 PM2 这样的进程管理器来运行后端服务器,并使用 Nginx 作为反向代理来处理前端和后端的传入请求。

使用以下命令安装 PM2:

npx prisma init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

对了,这就是我们的 Linux 环境设置。在下一节中,我们将从 GitHub 将代码库下载到远程服务器上,并配置 Nginx 服务器以使我们的应用程序在线运行。

在 Hetzner 上在线部署我们的应用程序

我假设您已经知道如何导航命令行。如果没有你可以谷歌一下。我们将更改目录并管理文件。首先克隆 GitHub 存储库和您的项目并将其复制到远程服务器上。使用以下命令作为参考:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model WatchlistItem {
  id          Int      @id @default(autoincrement())
  name        String
  image       String
  rating      Float
  description String
  releaseDate DateTime
  genre       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们的 Web 应用程序文件将存储在 /var/www 内。您可以使用命令 ls(用于列出文件)和 pwd(用于打印工作目录)来查看 Linux 操作系统上的所有文件。要了解有关 Linux 命令行的更多信息,请查看适用于初学者的 Linux 命令行教程。

现在我们的应用程序已经位于远程服务器上,我们可以创建后端和前端的生产版本。为此,我们只需 cd 进入 watchlist-tracker-app 项目内文件夹后端和客户端的根目录,然后运行如下所示的命令:

npx prisma migrate dev --name init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们使用 Bun 作为运行时,因此我们将使用 Bun 命令进行安装和构建步骤。

好吧,现在让我们配置我们的 Nginx 服务器。我们将使用 nano 终端编辑器在文件内编写代码。在终端中运行此命令,为我们的监视列表跟踪器应用程序打开 Nginx 文件:

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { PrismaClient } from '@prisma/client';

const app = new Hono();

app.use(cors());

const prisma = new PrismaClient();

app.get('/watchlist', async (c) => {
  const items = await prisma.watchlistItem.findMany();
  return c.json(items);
});

app.get('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const item = await prisma.watchlistItem.findUnique({
    where: { id: Number(id) },
  });
  return item ? c.json(item) : c.json({ error: 'Item not found' }, 404);
});

app.post('/watchlist', async (c) => {
  const data = await c.req.json();
  const newItem = await prisma.watchlistItem.create({ data });
  return c.json(newItem);
});

app.put('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const data = await c.req.json();
  const updatedItem = await prisma.watchlistItem.update({
    where: { id: Number(id) },
    data,
  });
  return c.json(updatedItem);
});

app.delete('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  await prisma.watchlistItem.delete({ where: { id: Number(id) } });
  return c.json({ success: true });
});

Bun.serve({
  fetch: app.fetch,
  port: 8000,
});

console.log('Server is running on http://localhost:8000');

export default app;
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

如果您不熟悉 Nano 代码编辑器,请查看此备忘单。

您只需将此配置复制并粘贴到文件中并保存即可。请务必将 server_name IP 地址替换为您自己的 Hetzner IP 地址:

"scripts": {
    "start": "bun run src/server.ts",
    "build": "bun build src/server.ts --outdir ./dist --target node"
},
登录后复制
登录后复制
登录后复制

Nginx 经常被用来充当反向代理服务器的角色。反向代理位于客户端(用户的浏览器)和后端服务器之间。它接收来自客户端的请求并将其转发到一台或多台后端服务器。后端处理完请求后,反向代理会将响应转发回客户端。在这样的设置中,Nginx 将成为传入流量的入口点,并将请求路由到特定服务,例如 Vite 前端或 API 后端。

Vite 生产预览版本在端口 4173 上运行,我们的后端服务器在端口 8000 上运行。如果您更改这些值,请确保也在这个 Nginx 配置文件中更新它们;否则服务器将无法工作。

如果尚未使用此命令启用 Nginx 站点,请启用它:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

现在测试 Nginx 配置以确保没有语法错误,并重新启动 Nginx 以使用以下命令应用更改:

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们快完成了。只剩下一步了。如果您现在在浏览器中访问您的 Hetzner IP 地址,您应该会看到类似 502 Bad Gateway 的错误。那是因为我们还没有运行服务器;首先,我们需要使用 PM2 同时运行两台服务器。因此,我们必须将PM2设置为在系统启动时启动,以便我们的应用程序始终在线。通过在终端中运行以下命令来执行此操作:

npx prisma init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

现在,我们必须让后端和前端服务器运行。从文件夹的根目录中运行这些命令。

让我们从后端服务器开始,因此在终端中运行以下命令:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model WatchlistItem {
  id          Int      @id @default(autoincrement())
  name        String
  image       String
  rating      Float
  description String
  releaseDate DateTime
  genre       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

最后,让我们运行客户端前端服务器,因此在终端中运行以下命令:

npx prisma migrate dev --name init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

您可以运行命令pm2 status来检查两台服务器是否在线并正在运行,如下所示。要了解所有其他 PM2 命令,请阅读 PM2 流程管理快速入门文档:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

您可以通过在终端中运行这些curl命令来测试服务器是否可访问。如果它们正常工作,您应该取回 HTML 代码:

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { PrismaClient } from '@prisma/client';

const app = new Hono();

app.use(cors());

const prisma = new PrismaClient();

app.get('/watchlist', async (c) => {
  const items = await prisma.watchlistItem.findMany();
  return c.json(items);
});

app.get('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const item = await prisma.watchlistItem.findUnique({
    where: { id: Number(id) },
  });
  return item ? c.json(item) : c.json({ error: 'Item not found' }, 404);
});

app.post('/watchlist', async (c) => {
  const data = await c.req.json();
  const newItem = await prisma.watchlistItem.create({ data });
  return c.json(newItem);
});

app.put('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const data = await c.req.json();
  const updatedItem = await prisma.watchlistItem.update({
    where: { id: Number(id) },
    data,
  });
  return c.json(updatedItem);
});

app.delete('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  await prisma.watchlistItem.delete({ where: { id: Number(id) } });
  return c.json({ success: true });
});

Bun.serve({
  fetch: app.fetch,
  port: 8000,
});

console.log('Server is running on http://localhost:8000');

export default app;
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

转到您的 Hetzner 服务器的 IP 地址。如果您所做的一切正确,您应该会看到您的应用程序已部署并在线!网站通常有域名,而 IP 地址在搜索栏中仍然隐藏。对于开发人员来说,购买域名然后更改名称服务器以将其连接到主机的服务器是相当常见的。这超出了本教程的范围,但您可以通过 Google 搜索轻松学习如何操作。 Namecheap 是我的首选域名注册商。

使用 DeployHQ 简化部署流程

是的,我们已经非常接近完成了。现在,最后一步是使用 DeployHQ 来简化部署过程。 DeployHQ 使部署变得简单,并且更适合安全目的。在在线服务器上更新代码库的传统方法是使用 git pull 从 GitHub 存储库获取最新更改。然而,执行 git pull 并不是一个好的做法,因为它可能会暴露 git 文件夹,并且网站很可能不会被缩小、丑化等。

DeployHQ 在这里发挥着至关重要的作用。它将修改后的文件安全地复制到配置的文件夹中,确保服务器上的 git 日志中看不到任何更改。这看起来像是一种权衡,但它是一项安全功能,可以让您放心部署的安全性。如果您熟悉 Vercel 或 Netlify 等平台,您会发现这些自动部署非常相似。在这种情况下,您的设置可以与 VPS 上的任何在线服务器一起使用。

值得一提的是,我们为在线 Linux 远程服务器创建一个非 root 用户非常重要。以 root 用户身份登录并不总是最佳实践;最好为另一个用户设置类似的权限。 DeployHQ 也不鼓励使用 root 用户登录。为了使 DeployHQ 正常工作,我们需要使用 SSH 登录我们的帐户。我们将在拥有 DeployHQ 帐户后执行此操作,因为我们必须将 DeployHQ SSH 公钥放在我们的在线 Ubuntu 服务器上。

首次注册时,DeployHQ 可以让您在 10 天内免费使用其所有功能,无需承担任何义务。之后,您的帐户将恢复为免费计划,允许您每天最多部署单个项目 5 次。

首先访问 DeployHQ 网站并通过单击下面看到的按钮之一创建一个帐户:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

好的,您现在应该看到欢迎来到 DeployHQ 屏幕,并带有“创建项目”按钮。单击按钮创建一个项目,如下所示:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

在下一个屏幕上,您需要创建一个新项目。因此,请为其命名,然后选择您的 GitHub 存储库。另外,为您的项目选择一个区域,然后创建一个项目:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

您现在应该看到服务器屏幕,如下所示。这意味着是时候为我们的远程服务器创建另一个 Linux 用户了,这样我们就不必依赖 root 用户了。

Deploying a React Watchlist Tracker App to Production Using DeployHQ

首先以 root 用户身份登录服务器,然后使用下面的命令创建一个新用户。将 new_username 替换为您要用于新用户的用户名:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

系统会要求您设置密码,并会提示输入您的全名、房间号等详细信息,您只需设置密码即可。您可以跳过其他提示步骤,按 Enter 键将其留空,直至全部消失。

将新用户添加到 sudo 组也是一个好主意,这样他们就可以拥有像 root 用户一样的管理权限。使用此处显示的命令执行此操作:

mkdir backend
cd backend
bun init -y
mkdir src
touch src/server.ts
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

新用户现在需要服务器的 SSH 访问权限。首先切换到新用户,然后使用以下命令为其创建 .ssh 目录:

bun add hono prisma @prisma/client
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

现在我们必须将本地公钥添加到服务器上的authorized_keys中,以便在本地计算机上使用以下命令复制您的公钥:

npx prisma init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

现在在服务器上打开authorized_keys文件,以便可以使用nano编辑器对其进行编辑:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model WatchlistItem {
  id          Int      @id @default(autoincrement())
  name        String
  image       String
  rating      Float
  description String
  releaseDate DateTime
  genre       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

将复制的公钥粘贴到此文件中。在保存文件之前,将 DeployHQ SSH 密钥从服务器页面复制并粘贴到同一文件中。当您选中使用 SSH 密钥而不是密码进行身份验证? ​​复选框时,您可以看到 SSH 密钥。现在保存文件并使用以下命令为文件设置正确的权限:

npx prisma migrate dev --name init
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

您现在可以测试新用户的 SSH 登录。首先,从远程服务器注销,然后尝试再次登录,但这次使用您创建的新用户而不是 root。请参阅下面的示例:

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { PrismaClient } from '@prisma/client';

const app = new Hono();

app.use(cors());

const prisma = new PrismaClient();

app.get('/watchlist', async (c) => {
  const items = await prisma.watchlistItem.findMany();
  return c.json(items);
});

app.get('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const item = await prisma.watchlistItem.findUnique({
    where: { id: Number(id) },
  });
  return item ? c.json(item) : c.json({ error: 'Item not found' }, 404);
});

app.post('/watchlist', async (c) => {
  const data = await c.req.json();
  const newItem = await prisma.watchlistItem.create({ data });
  return c.json(newItem);
});

app.put('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  const data = await c.req.json();
  const updatedItem = await prisma.watchlistItem.update({
    where: { id: Number(id) },
    data,
  });
  return c.json(updatedItem);
});

app.delete('/watchlist/:id', async (c) => {
  const id = c.req.param('id');
  await prisma.watchlistItem.delete({ where: { id: Number(id) } });
  return c.json({ success: true });
});

Bun.serve({
  fetch: app.fetch,
  port: 8000,
});

console.log('Server is running on http://localhost:8000');

export default app;
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

假设您所做的一切正确,您现在应该能够使用新用户登录。

现在我们终于可以完成服务器表单了。使用您的信息填写表格,请参阅下面的示例:

名称:观看列表跟踪器应用
协议:SSH/SFTP
主机名:11.11.111.111
港口:22
用户名:新用户名
使用 SSH 密钥而不是密码进行身份验证?: 已选中
部署路径:/var/www/watchlist-tracker-app

部署路径应该是服务器上 GitHub 存储库所在的位置。现在您可以继续创建服务器。如果您遇到任何问题,则可能是由于您的防火墙设置造成的,因此请阅读有关我应该允许哪些 IP 地址通过防火墙?的文档。

您现在应该看到如下所示的“新部署”屏幕:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

成功部署后,您应该会看到以下屏幕:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

最后一步是设置自动部署,以便当您将本地存储库中的更改推送到 GitHub 时,它们会自动部署到远程服务器。您可以从“自动部署”页面执行此操作,该页面位于 DeployHQ 帐户的左侧边栏。请参阅此处的示例:

Deploying a React Watchlist Tracker App to Production Using DeployHQ

我们都完成了;恭喜,您已经学会了如何构建全栈 React 应用程序、将代码库部署到 GitHub、在运行 Linux Ubuntu 的 VPS 上托管应用程序,以及使用 DeployHQ 设置自动部署。您的开发者游戏已升级!

结论

使用现代工具和技术构建像 Watchlist Tracker 这样的全栈 CRUD 应用程序是高效且令人愉快的。我们使用了相当强大的技术堆栈:后端的 Bun、Hono、Prisma ORM 和 SQLite,而前端的 Vite、Tailwind CSS 和 TanStack Router 有助于使其响应迅速且功能齐全。无论用户身在何处,Hetzner 将确保应用程序可靠且性能良好。

通过 DeployHQ 进行部署,使部署变得非常简单。您只需将更新直接从 Git 存储库推送到云服务器即可。您在存储库中所做的任何更改都将自动部署到您的生产服务器,以便最新版本的应用程序生效。这可以节省时间,因为自动化部署减少了与部署相关的错误数量,因此值得将其添加到任何形式的开发工作流程中。

本教程应该帮助您使用像 Hetzner 这样的 VPS 以及 DeployHQ 的自动 git 部署将各种应用程序部署到生产环境。

以上是使用 DeployHQ 将 React Watchlist Tracker 应用程序部署到生产环境的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板