[Next] Next.js+express+TypeScript基礎設定

張庭瑋
11 min readOct 29, 2020

--

React遇到SSR的問題時,這時常見的解決方案之一就是使用Next.js

今天要來記錄一下基礎的設定

有幾點要求

  1. Next.js
  2. 使用TypeScript
  3. 用express來寫課製server 並使用使用ES6語法 (即不使用require等)
  4. 使用eslint-airbnb

node.js更新

這時小黑窗說我node.js的版本不對,不能跑Create Next App

所以先更新node.js

npm cache clean -fsudo npm install -g nsudon [version.number]

當然使用nvm也沒有問題

nvm install <version>nvm use <version>// 查詢已安裝node.js
nvm ls
// 查詢可安裝node.js
nvm ls-remote

安裝Next及TypeScript

執行CNA及安裝TypeScript

yarn create next-app 專案名yarn add -D typescript @types/react @types/react-dom @types/node

安裝完之後把檔名是.js的jsx檔改成.tsx後執行yarn dev

此時Next會自動幫你建好tsconfig.json

整個就是很自動化

另外在 package.json 中新增 prettier 設定

...
"prettier": {
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"trailingComma": "none"
}

Eslint ( Airbnb )

yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-airbnb  eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-import

這裡原本想使用eslint-config-prettier / eslint-plugin-prettier來解決prettier跟eslint打架的問題

然後eslint-config-prettier會跟eslint-config-airbnb打架…讓airbnb某些警告收不到

後來放棄用eslint-prettier

還要在根目錄新增 .eslintrc.js 後 eslint 才會正式上工

// .eslintrc.jsmodule.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'react'],
parserOptions: {
ecmaFeatures: { jsx: true }
},
env: {
browser: true,
node: true
},
extends: [
'airbnb',
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended'
],
rules: {
semi: 'off',
'no-unused-vars': 'off',
'react/react-in-jsx-scope': 'off',
'react/jsx-props-no-spreading': 'off',
'react/jsx-one-expression-per-line': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'function-paren-newline': 'off',
'implicit-arrow-linebreak': 'off',
'jsx-quotes': ['error', 'prefer-single'],
'comma-dangle': ['error', 'never'],
'react/jsx-filename-extension': [
1,
{
extensions: ['.jsx', '.ts', '.tsx']
}
],
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
mjs: 'never'
}
]
},
settings: {
react: {
version: 'detect'
},
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx']
},
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx']
}
}
}
}

設定當然是可以依自己喜好調整

server內使用ES6語法

架設簡易的server可以地使用express

// server.js
const express = require('express')
const path = require('path')
const app = express()
const port = process.env.PORT || 3000
app.use(express.static(path.join(__dirname, 'dist')))

app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'))
})
app.listen(port, () => {
// do ...
})

然後使用指令

node server.js

然而這種寫法依現在的設定eslint會跳出警告

  1. eslint 警告:使用 require 而不是 import
  2. 改成 import 編譯時會問 import 是什麼
  3. 如果寫成 server.ts時node無法直接執行 .ts

這裡改來改去常有顧此失彼的問題,最後是使用 babel 編譯檔案後再執行的方式解決

yarn add -D @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/preset-env @babel/preset-typescript @types/express @babel/plugin-proposal-do-expressionsyarn add express nodemon

新增 server/index.ts

import next from 'next'
import express, { Request, Response } from 'express'
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
server.get('/apitest/side-menu', (_req: Request, res: Response) =>
res.json({ testArray: [1, 2, 3] })
)
server.get('*', (req: Request, res: Response) => handle(req, res))const PORT = process.env.PORT || 3000server.listen(PORT, () => {
console.log(`> Ready on port ${PORT}`)
})
})

這裡因為是讓 next 用的所以有些許調整

根目錄新增 .babelrc

// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"esmodules": true
}
}
],
"@babel/typescript",
"next/babel"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-do-expressions"
]
}

根目錄新增 nodemon.json

{
"watch": ["src", "server"],
"ext": "ts",
"ignore": ["src/**/*.spec.ts", "dist", "node_modules"],
"exec": "yarn build && node dist/index.js"
}

更改 package.json 的 script

  "scripts": {
...
"dev": "nodemon",
"build": "babel server --out-dir dist --extensions \".ts, .tsx\" --source-maps inline",
},

這樣 express 部分也可以用 ES6 + TypeScript 撰寫

當初看到 next.js 可以寫客製化的 server,以及本身 /pages/api 下可以寫 api

我曾經想試著把前後端都寫在 next.js 的專案下,如此一來只要用一個 port 就能跑前後端了,所以我才想把 server 部分也改成 TypeScript,提升後端的嚴謹度

沒想到在build時出現一個問題:使用getStaticProps方法做預先渲染時,因為 server 還沒啟動所以無法抓取資料 ( 正常程序是要先build完專案才執行專案 ),導致 build 失敗

解決方法有 3 個

1.另外開專案寫api

這樣就是前後端分開來弄,當然完全沒有問題

2.另外開小黑窗

next dev先run server,原本的小黑窗再跑build

可以是可以…但感覺…

3.使用getServerSideProps

getStaticProps不同的是getServerSideProps不會在build時先抓資料,而會在每次請求頁面時才抓取資料,所以可以避開這個問題

不過 getServerSideProps 的目的是抓取可能變動的資料進行 SSR,如果連資料不會變動而可以先 build 的頁面也讓getServerSideProps處理,那似乎違反了設計的目的了。

3種方法當然還是1最好,不過我也覺得3以測試目的隨便寫東西來用時也算方便的解決方案。

--

--