предисловие
Фреймворк SSR для рендеринга на стороне сервера на основе ReactNext.js, Фреймворк SSR для рендеринга на стороне сервера на основе VueNuxt.js.
Зачем нужен серверный рендеринг?
-
Уменьшите время белого экрана в верхней части страницы
-
SEO: Search Engine Optimization — поисковая оптимизация;
Проще говоря, когда запрашивается веб-страница, контент, отправленный с сервера, может быть просканирован поисковым роботом.
В настоящее время ключевые слова, найденные в поисковой системе, включены в это содержимое, поэтому информацию об этом URL-адресе легче отображать в результатах поиска~ -
рендеринг на стороне клиента: с появлением внешнего SPA большая часть контента страницы получает данные через асинхронные запросы на стороне браузера, а затем отрисовывает их;
А то, что присылается с сервера, это просто пустая оболочка без контента, и поисковые системы, естественно, ничего не сканируют~ -
рендеринг на стороне сервера: предыдущая внутренняя структура MCV использовала шаблоны для создания контента на сервере, а затем браузер получал данные и отображал их напрямую;
В это время содержимое веб-страницы уже следует за веб-сайтом, и для получения основного содержимого не требуется дополнительный асинхронный запрос,
Сканер поисковой системы может сканировать данные этих неасинхронных запросов~ -
SSR для интерфейсных фреймворков:в основномВнешний и внутренний изоморфизм,Агрегация интерфейса микросервисаИ т. д.; конечно, это самое простое, как уровень рендеринга, а интерфейс — это обязанность внутреннего босса;
Например, у всех React/Vue/Angular есть фреймворки, использующие Node.js для рендеринга данных на стороне сервера;
Внешний интерфейс также может продолжать использовать фреймворк React/Vue/Angular, но некоторые данные генерируются на стороне сервера.
Смотрите исходный код 👇здесь
Как видите, контент уже есть, когда он приходит с сервера:
Последующие изменения маршрутизации эквивалентныОдностраничное SPA, но обновляется по определенному маршруту, то этот маршрут такжерендеринг на стороне сервераиз
1. структура каталогов src
.
├── components
│ ├── header
│ ├── hello-world
│ └── layout
├── pages
│ ├── _app.tsx
│ ├── _error.tsx
│ ├── about.tsx
│ ├── detail.tsx
│ └── index.tsx
├── store
│ ├── home.ts
│ └── index.ts
└── styles
├── _error.scss
├── about.scss
├── detail.scss
└── index.scss
2. нпм-скрипт
tsc должен быть установлен
yarn install typescript -g
3. Заголовок страницы
установить заголовок страницы
import Head from 'next/head';
const Header = () => (
<Head>
<title>Next.js test</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
</Head>
);
Его можно использовать там, где вам нужно изменить заголовок страницы, например/aboutстраницу нужно изменитьtitleпримерно;
Это способ пошаговой модификации: если нет — добавить, если есть — изменить
// ...
<Head>
<title>about</title>
</Head>
// ...
4. Маршрутизация
pagesМаршруты автоматически генерируются в каталоге, и уровень может быть только один уровень следующим образом:
// 第一种写法
pages / home.tsx;
pages / detail.tsx;
styles / home.scss;
styles / detail.scss;
// 而不是
// 第二种写法
pages / home / index.tsx;
pages / home / home.scss;
pages/Это не может быть папка (обычно есть css, но если вы используете папку, фаза разработки нормальна, и файл scss будет сообщен, когда сборка не является компонентом React... Поэтому создайте новый снаружиstylesпапку, поместите файлы scss соответствующих компонентов маршрутизации)- другие папки, такие как
componentsОб ошибке не будет сообщено, вы можете использовать второй способ написания, стиль и компонент находятся в папке
4.1 Компоненты ссылки
- href: маршрут, например
href="about", он будет отображатьpage/about.tsxСодержание - как:
hrefПереименуйте, и адрес браузера покажет этот URL;
Маршрутизация href должна быть правильной, должен быть файл, который действительно существует в каталоге страницы. - prefetch: предварительная выборка, текущая страница будет использовать
href/as
URL-адрес браузера отображается как about1 следующим образом: если сервер не перехватывает маршрут, фактически отображается файл page/about.tsx, и фактический маршрут также остается прежним.
import Link from 'next/link';
<Link href="/about">
<a>About</a>
</Link>;
Если установлен псевдоним as и он отличается от исходного маршрута, необходимо установить другой маршрут на стороне сервера;
следующим образом:
- клиент
// ...
return (
<div>
<Link href="/detail?id=123" as="/detail/123">
<a style={linkStyle}>Detail</a>
</Link>
</div>
);
// ...
- Сервер
// server.ts
server.get('/detail/:id', (req: Req, res: http.ServerResponse) => {
app.render(req, res, '/detail', {
id: req.params.id,
});
});
prefetch
Для некоторых операций может потребоваться задержка, но данные можно предварительно получить с помощью предварительной выборки.
- Компонент ссылки
<Link href="/about" prefetch={true}>
<a>About</a>
</Link>
- императив
import { withRouter } from 'next/router';
function MyLink({ router }) {
return (
<div>
<a onClick={() => setTimeout(() => router.push('/dynamic'), 100)}>
A route transition will happen after 100ms
</a>
{// but we can prefetch it!
router.prefetch('/dynamic')}
</div>
);
}
export default withRouter(MyLink);
push/replace
- объект
import Router from 'next/router';
const handler = () => {
Router.push({
pathname: '/about',
query: { name: 'Zeit' },
});
};
function ReadMore() {
return (
<div>
Click <span onClick={handler}>here</span> to read more
</div>
);
}
export default ReadMore;
4.2 Компоненты не маршрутизации Получите параметры маршрутизации
Это то же самое, что и React, используйтеwithRouterПолучить параметры маршрута, но это изnext/routerЭкспортируется; также можно использовать функциональные компонентыuseRouter
// src/components/header/index.tsx
import Link from 'next/link';
import Head from 'next/head';
import React from 'react';
import styles from './header.scss';
const Header = () => {
return (
<div>
<Head>
<title>Next.js test</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
</Head>
<Link href="/">
<a className={styles.tag}>Home</a>
</Link>
<Link href="/about">
<a className={styles.tag}>About</a>
</Link>
<Link href="/detail?id=123" as="/detail/123">
<a className={styles.tag}>Detail</a>
</Link>
</div>
);
};
export default Header;
4.3 Преобразование маршрута
- Буду
href="/detail?id=123"иззапрашивать URL-адрес запросаПеревести вas="/detail/123"изURL параметров;
href/as являются статическими, при необходимости генерируются динамически.<Link>Только что
import Link from 'next/link';
<Link href="/detail?id=123" as="/detail/123">
<a className={styles.tag}>Detail</a>
</Link>;
- Соответствующий URL-адрес на стороне сервера:
/detail/:idМаршрутизация,
Добавить кparams 参数, а затем визуализировать/detailсоответствующийpage/detail.tsxдокумент;
Если на сервере не установлен соответствующий перехват маршрута, обновление приведет к ошибке 404.
// server.ts
// ...
function serverRun() {
const server = express();
// api接口
const controllers = require('./server/controller');
const apiRoute = ''; // '/web';
server.use(apiRoute, controllers);
// 匹配URL为 `/` 的路由,然后渲染 `/` 对应的 `page/index.tsx` 文件
server.get('/', (req: Req, res: http.ServerResponse) => {
app.render(req, res, '/');
});
// 匹配URL为 `/about` 的路由,然后渲染 `/about` 对应的 `page/about.tsx` 文件
server.get('/about', (req: Req, res: http.ServerResponse) => {
app.render(req, res, '/about');
});
// 匹配URL为 `/detail/:id` 的路由,添加 `params 参数`,然后渲染 `/detail` 对应的 `page/detail.tsx` 文件
server.get('/detail/:id', (req: Req, res: http.ServerResponse) => {
app.render(req, res, '/detail', {
id: req.params.id,
});
});
server.get('*', (req: http.IncomingMessage, res: http.ServerResponse) => {
return handle(req, res);
});
server.listen(3000, (err: any) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
}
4.4 Метод маршрутизации
Перехват маршрута Router.beforePopState
Выполняется на стороне браузера, компонент должен быть загруженcomponentDidMount/useEffectВыполнить, иначе будет сообщено об ошибке
// src/pages/index.tsx
import Link from 'next/link';
import { /* Router, */ useRouter } from 'next/router';
import React, { useEffect } from 'react';
import Layout from '@/components/layout';
import styles from '@/styles/index.scss';
/**
* 首页,路由为 '/'
* @param props
*/
const Home = () => {
const router = useRouter();
useEffect(() => {
// 路由拦截,会影响浏览器前进后退的渲染结果
// Router.beforePopState(({ url, as, options }: any) => {
// console.log('url: ', url);
// console.log('as: ', as);
// console.log('options: ', options);
// if (as === '/about') {
// console.log('about');
// return true;
// }
// return true;
// });
});
return (
<Layout>
<h1>{router.query.title}</h1>
<img
className={styles.img}
src="/static/images/4k-wallpaper-alps-cold-2283757.jpg"
/>
<div className={styles.content}>
<p>
This is our blog post. Yes. We can have a{' '}
<Link href="/link">
<a>link</a>
</Link>
. And we can have a title as well.
</p>
<h3>This is a title</h3>
<p>And here's the content.</p>
</div>
</Layout>
);
};
export default Home;
4.5 События маршрутизатора
Слушайте внутренние события маршрута
- routeChangeStart(url) — срабатывает, когда маршрут начинает меняться
- routeChangeComplete(url) — срабатывает после завершения изменения маршрута.
- routeChangeError(err, url) — срабатывает при возникновении ошибки смены маршрута
- beforeHistoryChange(url) — срабатывает перед изменением истории браузера
- hashChangeStart(url) — срабатывает, когда хеш начинает меняться
- hashChangeComplete(url) — срабатывает, когда хэш изменился
Пример:
import Router from 'next/router';
const handleRouteChange = (url) => {
console.log('App is changing to: ', url);
};
Router.events.on('routeChangeStart', handleRouteChange);
прыжок по маршруту
// 正常路由跳转,在about页面获取路由信息的时候,id为a11,
// 刷新页面则id为asss,所以尽量二者一致,避免不必要的问题
Router.push('/about?id=a11', '/about/asss');
- Мелкая маршрутизация: Мелкая маршрутизация
Измените URL-адрес страницы без выполнения getInitialProps,
Router.push('/about?id=a11', '/about/asss', { shallow: true });
5. Приложение
Компонент _app не будет уничтожен, если не будет обновлен вручную.
// src/pages/_app.tsx
import React from 'react';
import { NextComponentType } from "next";
import { Router } from 'next/router';
import App, { AppProps } from 'next/app';
interface Props {
Component: NextComponentType,
pageProps: AppProps,
router: Router
}
/**
* App
*/
class myApp extends App<Props> {
public constructor(props: Props) {
super(props);
}
public componentDidUpdate() {
console.log('router: ', this.props.router);
}
public componentDidMount() {
console.log('router: ', this.props.router);
}
public render() {
const { Component, pageProps } = this.props;
return (
<React.Fragment>
<Component {...pageProps} />
</React.Fragment>
);
}
}
export default myApp;
6. Компоненты
6.1 Свойство getInitialProps
Получите метод, вы можете получить данные в этом методе и отобразить их на сервере; он может использоваться только компонентами на страницах/Документация
Если текущий маршрут обновляется, он будет выполняться на стороне сервера, если он перескакивает с другого маршрута, он будет выполняться на стороне браузера без обновления страницы;
// src/pages/detail.tsx
import Head from 'next/head';
import { useRouter } from 'next/router';
import React from 'react';
import { inject, observer } from 'mobx-react';
import { homeStoreType } from '@/store/home';
import { Button, Row } from 'antd';
import Layout from '@/components/layout';
import styles from '@/styles/detail.scss';
function Detail(props: any) {
const router = useRouter();
const homeStore: homeStoreType = props.homeStore;
return (
<Layout>
<Head>
<title>Detail</title>
</Head>
<p className={styles.detail}>This is the detail page!</p>
id: {router.query.id}
<Row>count: {homeStore.count}</Row>
<Button onClick={() => homeStore.setCount(homeStore.count + 1)}>
count++
</Button>
</Layout>
);
}
Detail.getInitialProps = async function(context: any) {
/**
* 在当前路由刷新的话,context.req 为真,服务端才有 req/res,在命令行打印 'broswer';
* 如果是其他路由跳转过来没有刷新页面的话,context.req 为假,在浏览器控制台打印,
* 此时 document.title 是 跳转之前的页面 title;
*/
console.log('render-type: ', context.req ? 'server' : 'broswer');
return {
// data: 'detail'
};
};
const DetailWithMobx = inject('homeStore')(observer(Detail));
export default DetailWithMobx;
Метод получения параметраcontext = { pathname, query, asPath, req, res, err }:
- имя пути: фиксированная часть имени пути URL, как определено
/post/:id, тогда путь здесь/post - запрос: объект параметров запроса для URL
- asPath - определенный маршрут, такой как
/post/:id - req: HTTP request object (server only)
- res: HTTP response object (server only)
- err: ошибка во время рендеринга
6.2 Динамический импорт
использоватьdynamicа такжеimport()Реализовать динамические компоненты;
Второй параметр dynamic — это объект, а поле загрузки — это загрузка до завершения загрузки.
// src/components/about.tsx
import dynamic from 'next/dynamic';
import Head from 'next/head';
import React from 'react';
import Layout from '@/components/layout';
import styles from '@/styles/about.scss';
const Hello = dynamic(() => import('../components/hello-world/index'), {
loading: () => <p>...</p>,
});
function About() {
return (
<Layout>
<Head>
<title>about</title>
</Head>
<p className={styles.about}>This is the about page</p>
<Hello />
</Layout>
);
}
// About.getInitialProps = async function(context: any) {
// return {
// data: 'about'
// };
// }
export default About;
7. Псевдоним пути
использоватьbabel-plugin-module-alias, настройка веб-пакета напрямую недействительна
yarn add babel-plugin-module-alias -D
- настроить .babelrc
{
"plugins": [
["module-alias", { "src": "./src", "expose": "@" }]
],
"presets": [
"next/babel",
]
}
- tsconfig.json
{
"compilerOptions": {
...
"baseUrl": "src",
"paths": {
"@/*": ["./*"]
},
},
...
}
8. Используйте SCSS
Официальный плагин: @zeit/next-sass
8.1 SASS
Установить
npm install --save @zeit/next-sass node-sass
или
yarn add @zeit/next-sass node-sass
настроить
После настройки используйте то же, что и использование модулей CSS в реакции.
// next.config.js
const withSass = require('@zeit/next-sass');
module.exports = withSass({
cssModules: true, // 默认 false,即全局有效
cssLoaderOptions: {
importLoaders: 1,
localIdentName: '[local]___[hash:base64:5]',
},
});
или обычай
// next.config.js
const withSass = require('@zeit/next-sass');
module.exports = withSass({
webpack(config, options) {
return config;
},
});
использовать postcss
В корневом каталоге проекта создайте новый файл postcss.config.js.
// postcss.config.js
module.exports = {
plugins: {
autoprefixer: true,
},
};
9. и тд
Обратитесь к этому парнюстатья,
в основномcssModulesа такжеantd按需加载Для решения проблемы совместного использования другие можно сделать в соответствии с официальным сайтом antd, а antd-mobile нужно только изменить соответствующий antd на antd-mobile.
.babelrc
{
"plugins": [
// "transform-decorators-legacy",
["@babel/plugin-proposal-decorators", { "legacy": true }],
["module-alias", { "src": "./src", "expose": "@" }],
["import", { "libraryName": "antd", "style": "css" }]
],
"presets": [
"next/babel",
]
}
next.config.js
Ниже приведена публичная конфигурация, которая будет объединена в конфигурацию Next.config.js
// config/config.common.js
const path = require('path');
const cssLoaderGetLocalIdent = require('css-loader/lib/getLocalIdent.js');
// const isProd = process.env.NODE_ENV === 'production';
if (typeof require !== 'undefined') {
require.extensions['.css'] = (file) => {};
}
/* 公共配置 */
let configCommon = {
// assetPrefix: isProd ? 'https://cdn.mydomain.com' : '',
crossOrigin: 'anonymous',
cssModules: true,
cssLoaderOptions: {
importLoaders: 1,
localIdentName: '[local]___[hash:base64:5]',
getLocalIdent: (context, localIdentName, localName, options) => {
const hz = context.resourcePath.replace(context.rootContext, '');
// 排除 node_modules 下的样式
if (/node_modules/.test(hz)) {
return localName;
}
return cssLoaderGetLocalIdent(
context,
localIdentName,
localName,
options
);
},
},
distDir: 'next-build', // 构建输出目录,默认 '.next'
generateEtags: true, // 控制缓存的 etag,默认 true
pageExtensions: ['tsx', 'jsx', 'js', 'scss'], // pages文件夹下的文件后缀
webpack(config) {
if (config.externals) {
// 解决 打包css报错问题
const includes = [/antd/];
config.externals = config.externals.map((external) => {
if (typeof external !== 'function') return external;
return (ctx, req, cb) => {
return includes.find((include) =>
req.startsWith('.')
? include.test(path.resolve(ctx, req))
: include.test(req)
)
? cb()
: external(ctx, req, cb);
};
});
}
return config;
},
};
module.exports = configCommon;
10. Управление состоянием MobX
следовать этомуReact+TypescriptПочти то же самое, что и в одностраничном SPA-проекте, но рендеринг на стороне сервера Нетwindow; Так кеш сначала определяет, браузерный ли он, а потом использует API браузера (sessionStorage);
Однако обновление будет иметь процесс изменения данных, т.к._app.txsОн рендерится на стороне сервера, кеш восстанавливается в браузере, есть разница во времени, и будет предупреждение (на самом деле его можно игнорировать, кеш между сервером и клиентом не нужно синхронизировано, используйтеsessionStorageЭто еще и потому, что нет необходимости в долговременном кэшировании, конечно, его можно изменить наlocalStorage), выберите в соответствии с вашими потребностями
Одностраничный SPA не будет иметь этой проблемы, потому что он отображается в браузере.
const isBroswer: boolean = process.browser;
10.1 Вход в проект
// src/pages/_app.tsx
import { NextComponentType } from "next";
import { Router } from 'next/router';
import App, { AppProps } from 'next/app';
import React from 'react';
import { Provider } from 'mobx-react';
import store from '../store';
interface Props {
Component: NextComponentType,
pageProps: AppProps,
router: Router
}
/**
* App
*/
class myApp extends App<Props> {
public constructor(props: Props) {
super(props);
}
public componentDidUpdate() {
console.log('router: ', this.props.router);
}
public componentDidMount() {
console.log('router: ', this.props.router);
}
public render() {
const { Component, pageProps } = this.props;
return (
<React.Fragment>
<Provider {...store}>
<Component {...pageProps} />
</Provider>
</React.Fragment>
);
}
}
export default myApp;
10.2 Модули
Мониторинг использования данныхautorun, будет выполняться один раз при изменении данных; затем используйтеtoJSПреобразовать модуль вJS对象
// src/store/home.ts
import * as mobx from 'mobx';
// 禁止在 action 外直接修改 state
mobx.configure({ enforceActions: "observed"});
const { observable, action, computed, runInAction, autorun } = mobx;
const isBroswer: boolean = process.browser;
/**
* 所以缓存这里先判断一下是否浏览器,然后再去使用浏览器 API( `sessionStorage` );
* 不过会有一个闪现的过程,因为实际上 `_app.txs` 是在服务端渲染的,缓存是在浏览器恢复的,
* 有个时间差,而且会有警告,根据需求取舍吧
*/
let cache = isBroswer && window.sessionStorage.getItem('homeStore');
// 初始化数据
let initialState = {
count: 0,
data: {
time: '2019-11-20'
},
};
// 缓存数据
if (isBroswer && cache) {
initialState = {
...initialState,
...JSON.parse(cache)
}
}
class Home {
@observable
public count = initialState.count;
@observable
public data = initialState.data;
@computed
public get getTime() {
return this.data.time;
}
@action
public setCount = (_count: number) => {
this.count = _count;
}
@action
public setCountAsync = (_count: number) => {
setTimeout(() => {
runInAction(() => {
this.count = _count;
})
}, 1000);
}
// public setCountFlow = flow(function *(_count: number) {
// yield setTimeout(() => {}, 1000);
// this.count = _count;
// })
}
const homeStore = new Home();
// 数据变化后触发,数据缓存
autorun(() => {
const obj = mobx.toJS(homeStore);
isBroswer && window.sessionStorage.setItem('homeStore', JSON.stringify(obj));
});
export type homeStoreType = typeof homeStore;
export default homeStore;
10.3 Выход управления модулем
// src/store/index.ts
import homeStore from './home';
/**
* 使用 mobx 状态管理
*/
export default {
homeStore,
};
10.4 Использование компонентов
Вот использование функциональных компонентов, можно увидеть использование компонентов классаздесь
// src/pages/detail.tsx
import Head from 'next/head';
import { useRouter } from 'next/router';
import React from 'react';
import { inject, observer } from 'mobx-react';
import { homeStoreType } from '@/store/home';
import { Button, Row } from 'antd';
import Layout from '@/components/layout';
import styles from '@/styles/detail.scss';
function Detail(props: any) {
const router = useRouter();
const homeStore: homeStoreType = props.homeStore;
return (
<Layout>
<Head>
<title>Detail</title>
</Head>
<p className={styles.detail}>This is the detail page!</p>
id: {router.query.id}
<Row>count: {homeStore.count}</Row>
<Button onClick={() => homeStore.setCount(homeStore.count + 1)}>
count++
</Button>
<Button onClick={() => homeStore.setCountAsync(homeStore.count + 1)}>countAsync++</Button>
</Layout>
);
}
Detail.getInitialProps = async function(context: any) {
/**
* 在当前路由刷新的话,context.req 为真,服务端才有 req/res,在命令行打印 'broswer';
* 如果是其他路由跳转过来没有刷新页面的话,context.req 为假,在浏览器控制台打印,
* 此时 document.title 是 跳转之前的页面 title;
*/
console.log('render-type: ', context.req ? 'server' : 'broswer');
return {
data: 'detail',
};
};
const DetailWithMobx = inject('homeStore')(observer(Detail));
export default DetailWithMobx;
11. Сервер
11.1 server.ts
server.tsПосле изменения нужно вручную выполнить его в командной строкеtsc server.tsгенерироватьserver.js, можно исполнить (давайте пока что), можно и прямо в npm-скрипте написать, как автоматом скомпилировать + перезапустить сервис не знаю
next(opts: object)
opts имеет следующие свойства:
- dev (bool): Разрабатывать ли среду разработки — по умолчанию false
- dir (строка): Расположение следующего элемента — по умолчанию '.'
- тихий (bool): скрыть сообщение об ошибке, содержащее информацию о сервере — по умолчанию false
- conf (объект): то же, что и объект в next.config.js — по умолчанию {}
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({
dev,
dir: '.',
quiet: false,
conf: {},
});
Префикс динамического актива
Например, если вы используете cdn локально или онлайн, ресурсы не совпадают с сервером страницы.
// server.ts
const express = require('express');
const next = require('next');
import * as http from 'http';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
interface Req extends http.IncomingMessage {
params?: any;
}
app
.prepare()
.then(() => {
serverRun();
})
.catch((ex: any) => {
console.log(ex.stack);
process.exit(1);
});
function serverRun() {
const server = express();
// api接口
const controllers = require('./server/controller');
const apiRoute = ''; // '/web';
server.use(apiRoute, controllers);
// 匹配URL为 `/` 的路由,然后渲染 `/` 对应的 `page/index.tsx` 文件
server.get('/', (req: Req, res: http.ServerResponse) => {
app.render(req, res, '/');
});
// 匹配URL为 `/about` 的路由,然后渲染 `/about` 对应的 `page/about.tsx` 文件
server.get('/about', (req: Req, res: http.ServerResponse) => {
app.render(req, res, '/about');
});
// 匹配URL为 `/detail/:id` 的路由,添加 `params 参数`,然后渲染 `/detail` 对应的 `page/detail.tsx` 文件
server.get('/detail/:id', (req: Req, res: http.ServerResponse) => {
app.render(req, res, '/detail', {
id: req.params.id,
});
});
server.get('*', (req: http.IncomingMessage, res: http.ServerResponse) => {
return handle(req, res);
});
server.listen(3000, (err: any) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
}
11.2 Написать интерфейс
использовать экспресс по умолчанию
本来是要改成 koa 的,但是路由那里页面会渲染,服务端渲染的页面看网络请求确是 404,内容倒是渲染出来了(在 server-koa.ts )。 . .
модуль
// server/controllers/user.js
const Router = require('express').Router();
Router.get('/userInfo/:id', (req, res) => {
console.log('id: ', req.params.id);
res.send({
status: 200,
data: {
name: '小屁孩',
sex: '男',
age: '3',
},
message: '',
});
});
module.exports = Router;
Управление модулями
// server/controller.js
const Router = require('express').Router();
const user = require('./controllers/user');
Router.use('/user', user);
module.exports = Router;
Подключить к серверной службе
// server.ts
// ...
function serverRun() {
const server = express();
// api接口
const controllers = require('./server/controller');
const apiRoute = ''; // '/web';
server.use(apiRoute, controllers);
// ...
}
вызов интерфейса
// src/pages/about.tsx
fetch('/user/userInfo/2')
.then((res) => res.json())
.then((res) => {
console.log('fetch: ', res);
});
nginx развертывается локально, а запросы передаются через прокси (proxy_pass)прибытьnext-testслужбы (запущенной pm2), скриншот ответа на запрос:
12. Построить
npm script:
// package.json
...
"scripts": {
"dev": "node server.js",
"dev:tsc": "tsc server.ts",
"build": "next build",
"start": "cross-env NODE_ENV=production node server.js"
},
...
Среда разработки:
yarn dev
Пакет:
yarn build
затем генерируетсяnext-buildпапка (следующая сборка — это настроенный выходной каталог)
13. Развертывание
Не знаю зачем, положил проект в только что созданную папку html под nginx(/usr/local/etc/nginx/html/next-test) для запуска проекта и nginx доступ браузера всегда 502 (одностраничный SPA — это нормально). . . Вы можете запустить его в другом каталоге (/usr/local/website/next-test). . . Я не знаю, может это из-за macOS, найдите время попробовать на linux~
так что клади предметы/usr/local/website/ниже
13.1 pm2
Используя pm2, вы можете протестировать локально, но окончательное развертывание все равно будет сервером.
yarn global add pm2
Войдите в каталог проекта в терминале, а затем:
полное командование
pm2 start yarn --name "next-test" -- run start
сценарий
Root Directory Project deploy.sh:
#deploy.sh
pm2 start yarn --name "next-test" -- run start
Терминал входит в каталог проекта:
. deploy.sh
Несколько команд pm2:
- pm2 show [id]: показать информацию о приложении pm2
- Список pm2: показывает обзор всех приложений pm2.
- pm2 stop [id]/all: остановить приложение, вы можете удалить несколько, через пробел или остановить все
- pm2 delete [id]/all: удалить приложение, можно удалить несколько, разделенных пробелами или удалить все
- pm2 monit: отслеживать статус приложения, запущенного pm2
- перезапуск pm2: перезапустить
- так далее
13.2 Nginx
pm2 service runner, nginx отвечает за открытие сервиса http, вот простое использование
Несколько команд nginx
- нгинкс: запустить нгинкс
- nginx -t: проверить правильность конфигурации nginx.conf
- NGINX -S STOP: STOP NGINX
- NGINX -S RELOAD: перезапустите Nginx
- так далее
Конфигурация протестирована локально (mac)
код сайта
/usr/local/websiteВниз
Конфигурация nginx.conf:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
gzip on;
upstream next-test {
server 127.0.0.1:3000; # next-test 启动的服务端口
}
# next-test
server {
listen 80;
server_name localhost; #这里配置域名
#charset koi8-r;
#access_log logs/host.access.log main;
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location / {
proxy_pass http://127.0.0.1:3000; #请求将会转发到next-test的node服务下
proxy_redirect off;
# root html;
index index.html index.htm;
}
}
# movie-db: 单页面SPA
server {
listen 81;
server_name localhost; #这里配置域名
root /usr/local/website/movie-db/;
location / {
try_files $uri $uri/ @router;
}
location @router {
rewrite ^.*$/index.html last;
}
# error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
include servers/*;
}
что делать дальше
база данных
MySQL/MongoDB эти два
GrahpQL
кажется, называетсяЯзык запросов API, в основном для агрегации интерфейсов; для агрегации серверных интерфейсов микросервисов, а затем интерфейсной странице нужно только запроситьАгрегированный интерфейс, нет необходимости в нескольких запросахНебольшой интерфейс внутреннего микросервиса; не знаю, правильно ли я понимаю
некоторые проблемы
-
С основного маршрута на дополнительный маршрут, а затем обновить, браузер возвращается, URL-адрес меняется, но содержимое остается прежним! это ошибка,issues#9378, решить
-
После обновления некоторых маршрутов ввод других маршрутов приводит к потере стилей, см. запрос
styles.chunk.cssи нет соответствующего css, ноscss+css modulesНу собственно трансформируется (пока среда разработки вроде бы и не встречалась после упаковки) -
Похоже проблема с next.js при использовании koa.Отрисовываемая сервером страница увидит 404 из сетевого запроса, но страница отрисовывается либо это динамическая маршрутизация(
/detail/:id) 404. . . У сервисов Node.js, построенных только с помощью koa, таких проблем нет (здесь )
Ссылаться на
- Документацияnextjs.org
- В интернете тоже есть статьи