Node

В этом посте я проведу вас по пути создания приложения Next.js, обслуживаемого клиентским сервером Express c применением фреймворка Typescript. Полную версию проекта можно увидеть на GitHub.

Настройка проекта Next.js

Сначала создадим папку проекта:

mkdir -p next-express-typescript
cd ./next-express-typescript

Инициализируем файл package.json и установим зависимости:

npm init -y
npm install react react-dom next

Добавим скрипты (описания команд) в соответствующий раздел файла package.json:

"scripts": {
   "dev": "next",
   "build": "next build",
   "start": "next start"
 }

Создадим первую страницу проекта:

mkdir -p src/pages
touch src/pages/index.js

Наполним ее некоторым содержимым:

function IndexPage() {
 return <div>Hello World Next - Typescript - Express</div>;
}
 
export default IndexPage;

Запустим команду npm run dev и перейдем на http://localhost:3000, чтобы посмотреть созданную страницу:

Конфигурация TypeScript

В основном, Next.js предоставляет быстрый способ настройки TypeScript. Создадим пустой файл tsconfig.json:

touch tsconfig.json

Снова запустим команду npm run dev. После чего увидим предложение от Next.js установить необходимые пакеты:

Установим их. На этом установка TypeScript завершится.

npm install --save-dev typescript @types/react @types/node
mv src/pages/index.js src/pages/index.tsx

Снова запустим сервер npm run dev. Теперь все должно работать правильно.

Настройка Express

При выполнении команды npm run dev Next.js запускает свой сервер, но возможна программная установка собственного сервера Express. Необходимо установить Express, а также зависимости Typescript для него.

npm install express
npm install --save-dev @types/express ts-node

Создадим папку server и точку входа для сервера:

mkdir -p server
touch server/index.ts

В файле index.ts напишем следующий код:

import express, { Request, Response } from "express";
import next from "next";

const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const port = process.env.PORT || 3000;

(async () => {
  try {
    await app.prepare();
    const server = express();
    server.all("*", (req: Request, res: Response) => {
      return handle(req, res);
    });
    server.listen(port, (err?: any) => {
      if (err) throw err;
      console.log(`> Ready on localhost:${port} - env ${process.env.NODE_ENV}`);
    });
  } catch (e) {
    console.error(e);
    process.exit(1);
  }
})();

Теперь отредактируем скрипт dev в package.json

"scripts": {
  "dev": "ts-node server/index.ts",
  ...
}

ts-node — указывает на исполняемый файл TypeScript и REPL для Node.js с поддержкой source map.

В очередной раз запускаем npm run dev. И… получаем ошибку.

import express from "express";
       ^^^^^^^

Проблема заключается в tsconfig.json, в строке “module”: “esnext”.

Как правило, Next.js заставляет TS компилироваться с шаблоном ESNext, но express был собран по шаблону commonjs. Если изменить значение esnext на commonjs, Next.js автоматически вернет его обратно.

Чтобы решить эту проблему, давайте создадим еще один конфигурационный файл:

touch tsconfig.server.json

И добавим в него следующие строки кода из tsconfig.json:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "outDir": "dist",
    "noEmit": false
  },
  "include": ["server"]
}

Рассмотрим каждую строчку в отдельности:

· "extends": "./tsconfig.json": получаем все опции из tsconfig.json;

· "compilerOptions.module": "commonjs": используем commonjs как шаблон, чтобы была возможна дальнейшая совместная работа express и next;

· "compilerOptions.outDir": "dist": эта опция используется для продакшна. Файлы с расширением .ts в директории server будут компилироваться в файлы с расширением .js и отправляться в директорию dist.

· "compilerOptions.noEmit": true: еще одна настройка для продакшна. Next.js использует в основном babel для компиляции TypeScript, поэтому TS-компилятор не будет понимать файлы с расширением .js. Переопределение значения сообщит TS-компилятору о том, что он может транслировать и выводить файлы с server.

· "include": ["server"]: эта опция также используется для продакшна. Она позволяет компилятору транслировать только файлы с расширением .ts в директорию server/.

Будем использовать эту конфигурацию для локальной разработки. Снова отредактируем скрипт dev:

"dev": "ts-node --project tsconfig.server.json server/index.ts"

Выполняем npm run dev, переходим на http//localhost:3000… Все работает!

Исправление ошибок ненайденных страниц

Некоторые могут столкнуться с ошибкой RangeError: Invalid array length , которая появляется в консоли при запуске пользовательского сервера.

Кроме того, при посещении незнакомой страницы, скажем http://localhost:3000/no-exist-page, получим Internal Server Error вместо сообщения 404 not found. Это потому, что Next.js больше не обрабатывает ошибки при интеграции с пользовательским сервером. Решением этой проблемы будет создание кастомной страницы ошибки.

touch src/pages/_error.tsx

Откроем созданный файл и напишем в нем такой код:

import { NextPageContext } from "next";

const Error = ({ statusCode }) => {
  return (
    <p>
      {statusCode
        ? `An error ${statusCode} occurred on server`
        : "An error occurred on client"}
    </p>
  );
};

Error.getInitialProps = ({ res, err }: NextPageContext) => {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
  return { statusCode };
};

export default Error;

Снова посещаем страницу http://localhost:3000/no-exist-page, где видим наше сообщение об ошибке.

Production

Перед запуском сервера в продакшне необходимо скомпилировать файлы сервера и Next.js в файлы с расширением .js. Изменим scripts в package.json.

"scripts": {
 "build:server": "tsc --project tsconfig.server.json",
 "build:next": "next build",
 "build": "npm run build:next && npm run build:server",
 "start": "NODE_ENV=production node dist/index.js"
}

Рассмотрим каждую строку в отдельности:

· build: server: сборка кода сервера в каталог dist;

· build: next: сборка приложения Next.js;

· build: совместный запуск build: server и build: next;

· start: запуск сервера. Строчка запускает node dist/index.js вместо next start, потому что теперь мы позволяем express обрабатывать сервер.

Запустим сборку npm run build и увидим такой результат:

Теперь запустим npm start и посмотрим, как наше приложение работает в продакшне.

>  NODE_ENV=production node dist/index.js
>  Ready on localhost:3000 – env production

В заключение

Теперь у вас есть шаблон, чтобы начать Next.js-проект с применением сервера Express и TypeScript! Есть еще некоторые продвинутые технологии, осуществляющие тестирование кода Next и Express… Но о них как-нибудь в другой раз.

Надеюсь, было полезно! Спасибо за чтение и удачи в программировании!

Читайте также:


Перевод статьи Tien: Set Up Next.js with a Custom Express Server + Typescript.