Создание приложения SSR с помощью React, React Router и Vite


Введение

В последние годы существует два чрезвычайно популярных способа рендеринга веб-страниц: одностраничные приложения и рендеринг на стороне сервера.

Существует несколько инструментов и шаблонов, которые помогают нам настроить проект React для создания SPA, например, знаменитый create-react-app и vite. Но когда мы говорим о SSR, мы обычно имеем в виду фреймворки, такие как Next.js, Remix и Razzle.

Однако, хотя существует множество статей и туториалов о том, как перенести существующее React-приложение на Next.js, не так много материалов о том, как преобразовать текущий проект с React на SSR без использования фреймворка.

В этом руководстве мы вместе рассмотрим, как можно преобразовать React SPA с помощью Vite в SSR.

Что мы будем использовать?

В этом уроке мы будем использовать следующие технологии для создания SSR-приложения:

  • React — react — это инструмент для создания компонентов пользовательского интерфейса.
  • React Router — помогает управлять навигацией между страницами различных компонентов в приложении react
  • Vite — инструмент сборки, использующий доступность ES-модулей в браузере и компиляцию в нативный бандлер
  • h3 — минималистичный и простой фреймворк для node.js
  • sirv — простое и удобное промежуточное программное обеспечение для обслуживания статических файлов
  • listhen — элегантный http listener

Предварительные условия

Прежде чем продолжить, вам понадобятся

  • Node
  • Yarn
  • TypeScript
  • React

Кроме того, ожидается, что вы обладаете базовыми знаниями об этих технологиях.

Создание проекта Vite

В качестве первого шага создайте каталог проекта и перейдите в него:

yarn create vite react-ssr --template react-ts
cd react-ssr
Войти в полноэкранный режим Выйти из полноэкранного режима

Далее установим маршрутизатор react:

yarn add react-router-dom
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь мы можем создать наши страницы внутри src/pages/:

// @/src/pages/Home.tsx
export const Home = () => {
  return <div>This is the Home Page</div>;
};
Войти в полноэкранный режим Выход из полноэкранного режима
// @/src/pages/Other.tsx
export const Home = () => {
  return <div>This is the Other Page</div>;
};
Войти в полноэкранный режим Выход из полноэкранного режима
// @/src/pages/NotFound.tsx
export const NotFound = () => {
  return <div>Not Found</div>;
};
Войти в полноэкранный режим Выход из полноэкранного режима

Затем мы переименуем наш App.tsx в router.tsx и, как вы уже догадались, именно в этом файле мы определим каждый из маршрутов нашего приложения:

// @/src/router.tsx
import { Routes, Route } from "react-router-dom";

import { Home } from "./pages/Home";
import { Other } from "./pages/Other";
import { NotFound } from "./pages/NotFound";

export const Router = () => {
  return (
    <Routes>
      <Route index element={<Home />} />
      <Route path="/other" element={<Other />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
};
Вход в полноэкранный режим Выход из полноэкранного режима

Когда страницы нашего приложения созданы, а маршруты определены, мы можем приступить к работе над нашими входными файлами.

В настоящее время единственным входным файлом в нашем проекте является main.tsx, который мы переименуем в entry-client.tsx. Этот файл будет отвечать за точку входа в связку браузера и будет выполнять гидратацию страницы.

// @/src/entry-client.tsx
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";

import { Router } from "./router";

ReactDOM.hydrateRoot(
  document.getElementById("app") as HTMLElement,
  <BrowserRouter>
    <Router />
  </BrowserRouter>
);
Вход в полноэкранный режим Выход из полноэкранного режима

Следующий файл входа, который мы создадим, это entry-server.tsx, в котором мы экспортируем функцию render(), которая будет получать местоположение (путь) в аргументах, затем рендерить страницу, которая была запрошена, и в конце рендерить в строку (которая позже будет добавлена в index.html на сервере узла).

// @/src/entry-server.tsx
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";

import { Router } from "./router";

interface IRenderProps {
  path: string;
}

export const render = ({ path }: IRenderProps) => {
  return ReactDOMServer.renderToString(
    <StaticRouter location={path}>
      <Router />
    </StaticRouter>
  );
};
Войти в полноэкранный режим Выход из полноэкранного режима

И последнее, но не менее важное: нам нужно внести изменения в index.html, чтобы он выглядел следующим образом:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite SSR + React + TS</title>
  </head>
  <body>
    <div id="app"><!--ssr-outlet--></div>
    <script type="module" src="/src/entry-client.tsx"></script>
  </body>
</html>
Вход в полноэкранный режим Выход из полноэкранного режима

Когда клиентская часть нашего приложения создана, мы можем перейти к следующему шагу.

Создание сервера Node

Прежде чем начать писать код, нам нужно установить необходимые зависимости:

yarn add h3 sirv listhen
Войти в полноэкранный режим Выйти из полноэкранного режима

Сервер node будет отвечать за обслуживание нашего приложения в среде разработки и производства. Но эти две среды совершенно разные, и каждая из них имеет свои требования.

Идея заключается в том, что в среде разработки мы будем использовать vite во всем процессе, то есть он будет использоваться как сервер разработки, он будет преобразовывать html и рендерить страницу.

В то время как в производственной среде мы хотим обслуживать статические файлы, которые будут находиться в папке dist/client/, а также JavaScript, который мы будем запускать для рендеринга страниц, будет находиться в dist/server/, и именно его мы будем использовать. Вот пример:

// @/server.js
import fs from "fs";
import path from "path";

import { createApp } from "h3";
import { createServer as createViteServer } from "vite";
import { listen } from "listhen";
import sirv from "sirv";

const DEV_ENV = "development";

const bootstrap = async () => {
  const app = createApp();
  let vite;

  if (process.env.NODE_ENV === DEV_ENV) {
    vite = await createViteServer({
      server: { middlewareMode: true },
      appType: "custom",
    });

    app.use(vite.middlewares);
  } else {
    app.use(sirv("dist/client", {
        gzip: true,
      })
    );
  }

  app.use("*", async (req, res, next) => {
    const url = req.originalUrl;
    let template, render;

    try {
      if (process.env.NODE_ENV === DEV_ENV) {
        template = fs.readFileSync(path.resolve("./index.html"), "utf-8");

        template = await vite.transformIndexHtml(url, template);

        render = (await vite.ssrLoadModule("/src/entry-server.tsx")).render;
      } else {
        template = fs.readFileSync(
          path.resolve("dist/client/index.html"),
          "utf-8"
        );
        render = (await import("./dist/server/entry-server.js")).render;
      }

      const appHtml = await render({ path: url });

      const html = template.replace(`<!--ssr-outlet-->`, appHtml);

      res.statusCode = 200;
      res.setHeader("Content-Type", "text/html").end(html);
    } catch (error) {
      vite.ssrFixStacktrace(error);
      next(error);
    }
  });

  return { app };
};

bootstrap()
  .then(async ({ app }) => {
    await listen(app, { port: 3333 });
  })
  .catch(console.error);
Вход в полноэкранный режим Выход из полноэкранного режима

После объяснения работы сервера node и приведенного примера мы можем добавить следующие скрипты в package.json:

{
  "dev": "NODE_ENV=development node server",
  "build": "yarn build:client && yarn build:server",
  "build:client": "vite build --outDir dist/client",
  "build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server",
  "serve": "NODE_ENV=production node server"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это скрипты, которые позволяют запустить приложение. Если вы хотите запустить среду разработки, просто запустите yarn dev, если вы хотите собрать приложение, просто используйте yarn build, а yarn serve — для запуска производственной среды.

Если вы перейдете по адресу http://localhost:3333, у вас должно быть запущено веб-приложение.

Заключение

Как всегда, я надеюсь, что статья показалась вам интересной, и что она помогла вам перевести существующее приложение с React with Vite на SSR более простым и удобным способом.

Если вы нашли ошибку в статье, пожалуйста, сообщите мне об этом в комментариях, чтобы я мог ее исправить. Прежде чем закончить, если вы хотите получить доступ к исходному коду этой статьи, я оставляю здесь ссылку на репозиторий github.

Хорошего дня!

Оцените статью
devanswers.ru
Добавить комментарий