龙空技术网

next.js+react+typescript react服务端渲染构建项目,构建到发布

局外人LZ 108

前言:

现在大家对“复制assets文件”大约比较着重,我们都想要了解一些“复制assets文件”的相关内容。那么小编同时在网络上汇集了一些对于“复制assets文件””的相关文章,希望同学们能喜欢,咱们一起来了解一下吧!

简介:该教程兼容pc+移动端,兼容原理:同时封装了pc框架antd、和移动端框架antdMobile,根据不同客户端类型渲染不同的组件,同一个组件需要根据客户端类型同时封装pc和移动端,如果觉得开发麻烦,可忽略兼容部分教程,根据需要分别构建pc、和移动端

antd官网:antd-mobile官网:next.js: react:redux:

一、介绍

Next.js,这是一个 React 的同构应用开发框架。

直观的、 基于页面 的路由系统(并支持 动态路由)预渲染。支持在页面级的 静态生成 (SSG) 和 服务器端渲染 (SSR)自动代码拆分,提升页面加载速度具有经过优化的预取功能的 客户端路由内置 CSS 和 Sass 的支持,并支持任何 CSS-in-JS 库开发环境支持 快速刷新利用 Serverless Functions 及 API 路由 构建 API 功能完全可扩展

二、构建项目

yarn create next-app “文件名” --typescriptyarn dev

三、调整项目

文件目录_app.tsx

import type { AppProps } from "next/app";export default function App({ Component, pageProps }: AppProps) {  return <Component {...pageProps} />}
index.tsx
import {NextPage} from "next";const Home: NextPage = (props) => {      return <div>dsada</div>};export default Home

三、静态资源assets

创建assets>css、assets>font、assets>img安装依赖

yarn add sass
集成字体图标,下载阿里icon库,解压后把压缩包里的文件复制到assets>font
<i class="iconfont icon-usename"></i>
css文件下分别创建globals.scss、iframe.scss、normalize.scss、variable.scss
//globals.scss 全局样式文件body{  font-size: $font_size!important;}
//iframe.scss 公共样式导入@import "./globals";@import "./normalize";@import "../font/iconfont.css";
//normalize.scss 同一浏览器样式,下载后放入该文件中
//variable.scss 全局变量文件$primary-color: red;/*** 字体大小*/$font_size: 14px;//基础字体大小$sm_font_size: 12px;//小号字体$bg_font_size: 16px;//大号字体$xl_font_size: 20px;//超大号字体/*** icon 大小*/$icon_size: $font_size;//默认字体$bg_icon_size: $bg_font_size;//大号字体$sm_icon_size: $sm_font_size;//小号字体$xl_icon_size: $xl_font_size;//超大号字体/*** button 颜色、大小*/$btn_primary: #1677ff;$btn_danger: #ff4d4f;/*** h1-h5标签字体大小*/$h1_font_size: 38px;//h1字体大小$h2_font_size: 30px;//h2字体大小$h3_font_size: 24px;//h3字体大小$h4_font_size: $xl_font_size;//h4字体大小$h5_font_size: $bg_font_size;//h5字体大小
配置引入路径tsconfig.json
"paths": {    ...    "@css/": [        "./src/assets/css/"    ],    "@img/": [        "./src/assets/img/"    ],    ...}
引入全局样式,修改_app.tsx
import type { AppProps } from "next/app";import "@css/iframe.scss";export default function App({ Component, pageProps }: AppProps) {  return <Component {...pageProps} />}
引入全局sass变量,next.config.js
const path = require("path");/** @type {import('next').NextConfig} */const nextConfig = {  ...  sassOptions:{    includePaths: [path.join(__dirname, "./src/assets/css")],    prependData: "@import 'variable.scss';"  },  ...}module.exports = nextConfig
配置antd-mobile主题,,新建 css>antdMobileTheme.scss
:root:root {  --adm-color-primary: #ff4d4f;}
配置antd主题新建pages>antTheme.module.scss
/* antd 主题配置 * 详细配置可参考 */:export {  colorPrimary: $primary-color;  fontSize: $font_size;  fontSizeHeading1: $h1_font_size;  fontSizeHeading2:$h2_font_size;  fontSizeHeading3:$h3_font_size;  fontSizeHeading4:$h4_font_size;  fontSizeHeading5:$h5_font_size;  fontSizeLG:$bg_font_size;  fontSizeSM:$sm_font_size;  fontSizeXL:$xl_font_size;  fontSizeIcon:$sm_icon_size;}

修改_app.tsx

import type { AppProps } from "next/app";import {ConfigProvider} from "antd";import them from "./antTheme.module.scss";import "@css/iframe.scss";export default function App({ Component, pageProps }: AppProps) {  return  <ConfigProvider theme={{token: them}}>    <Component {...pageProps}/>  </ConfigProvider>}
集成postcss安装依赖postcss-px-to-viewport-8-plugin
yarn add postcss-px-to-viewport-8-plugin --dev
根目录新建postcss.config.js
//postcss.config.jsmodule.exports = {    plugins: {        "postcss-px-to-viewport-8-plugin": {            viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度            viewportHeight: 912, // 视窗的高度,对应的是我们设计稿的高度,可以不设置            unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)            viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw            propList: ['*'],            selectorBlackList: [/^.pc/],            minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值            mediaQuery: false, // 允许在媒体查询中转换`px`,            exclude: [/pc.module/,/antTheme.module.scss/,/braft-editor/], //设置忽略文件,用正则做目录名匹配        }    },};

参数

说明

类型

默认值

unitToConvert

需要转换的单位,默认为 px

string

px

viewportWidth

设计稿的视口宽度,如传入函数,函数的参数为当前处理的文件路径,函数返回

undefind

跳过转换

number | Function

320

unitPrecision

单位转换后保留的精度

number

5

propList

能转化为 vw 的属性列表

string[]

['*']

viewportUnit

希望使用的视口单位

string

vw

fontViewportUnit

字体使用的视口单位

string

vw

selectorBlackList

需要忽略的 CSS 选择器,不会转为视口单位,使用原有的 px 等单位

string[]

[]

minPixelValue

设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换

number

1

mediaQuery

媒体查询里的单位是否需要转换单位

boolean

false

replace

是否直接更换属性值,而不添加备用属性

boolean

true

landscape

是否添加根据

landscapeWidth

生成的媒体查询条件

@media (orientation: landscape)

boolean

false

landscapeUnit

横屏时使用的单位

string

vw

landscapeWidth

横屏时使用的视口宽度,,如传入函数,函数的参数为当前处理的文件路径,函数返回

undefind

跳过转换

number

568

exclude

忽略某些文件夹下的文件或特定文件,例如 node_modules 下的文件,如果值是一个正则表达式,那么匹配这个正则的文件会被忽略,如果传入的值是一个数组,那么数组里的值必须为正则

Regexp

undefined

include

需要转换的文件,例如只转换 'src/mobile' 下的文件 (

include: /\/src\/mobile\//

),如果值是一个正则表达式,将包含匹配的文件,否则将排除该文件, 如果传入的值是一个数组,那么数组里的值必须为正则

Regexp

undefined

四、集成redux

安装依赖

yarn add redux react-redux redux-sagayarn add @types/react-redux @types/redux-saga next-redux-wrapper redux-devtools-extension --dev
创建redux>reducers、redux>sagas文件夹配置引入路径tsconfig.json
"paths": {    ...    "@reducers/*": [      "./src/redux/store/reducers/*"    ],    "@sagas/*": [      "./src/redux/store/sagas/*"    ],    "@store/*": [      "./src/redux/store/*"    ],    ...}
创建第一个store,redux>reducers>mobileStore.tsx
/** * @description 该store,判断是否是移动端 * *//** * @description 定义相关接口或者枚举 * */export enum MobileStoreActionEnum {    INIT="mobileStoreInit",    CHANGE="mobileStoreChange"}export type MobileStoreStateType = boolean;interface MobileStoreActionInterface{    type: MobileStoreActionEnum,    payload:MobileStoreStateType}/** * @description store逻辑 * */const mobileInitState:MobileStoreStateType = false;const mobileStore = (state:MobileStoreStateType =mobileInitState, action: MobileStoreActionInterface):MobileStoreStateType => {    switch (action.type) {        case MobileStoreActionEnum.INIT:            return state        case MobileStoreActionEnum.CHANGE:            return action.payload        default:            return state    }}export default mobileStore;
创建第一个sagaStore,redux>reducers>demoStore.tsx,异步store
/** * @description 定义相关接口或者枚举 * */export enum DemoStoreActionEnum{    WATCH='watchDemoStore',    CHANGE='demoStoreChange'}interface DemoStoreStateInterface {    num:number}export interface DemoStoreActionInterface {    type: DemoStoreActionEnum    payload: DemoStoreStateInterface}/** * @description store逻辑 * */const initState:DemoStoreStateInterface = {    num: 100}const demoStore = (state:DemoStoreStateInterface = initState, action: DemoStoreActionInterface):DemoStoreStateInterface => {    switch (action.type) {        case DemoStoreActionEnum.CHANGE:            return action.payload        default:            return state    }};export default demoStore;
依次创建redux>sagas>demo.tsx、redux>sagas>mainSaga.tsxsaga的应用场景是复杂异步,如长时事务LLT(long live.transcation)等业务场景。方便测试,可以使用takeEvery打印logger。提供takeLatest/takeEvery/throttle方法,可以便利的实现对事件的仅关注最近事件、关注每一次、事件限频提供cancel/delay方法,可以便利的取消、延迟异步请求提供race(effects),[…effects]方法来支持竞态和并行场景提供channel机制支持外部事
import { call, put, takeEvery, takeLatest,take,all,race,throttle,delay,fork,cacel,cancelled} from 'redux-saga/effects';takeEvery:被调用的任务无法控制何时被调用, 它们将在每次 action 被匹配时一遍又一遍地被调用。并且它们也无法控制何时停止监听。take:与takeEver相反,与 action 只会监听一次,使用一次就销毁takeLatest:每次 action 被匹配,当有action正在匹配,会放弃正在匹配的action,执行最新的call: saga通过 Generator函数实现,在yield函数后执行effect,其中call是用于执行某些异步操作的。put: 和上面的call一样,中间件提供put 来把action丢到中间件中去dispatch,好处同样是便于测试all: 同步执行多个任务使需要用到 yield all([call(fetch, '/users'),call(fetch, '/repos')])race: 和promise中的race一个概念,执行多个任务,受到响应后则继续执行 yield race({posts: call(fetchApi, '/posts'),timeout: call(delay, 1000)})fork:fork和take不同,take会和call一样阻塞代码的执行,知道结果返回,fork则不会,它会将任务启动并且不阻塞代码的执行,fork会返回一个task,可以用cacel(task)来取消任务cacel:来取消任务cancelled:如果当前任务,被cacel取消,则返回truethrottle:节流
//redux>sagas>demo.tsximport {call, put, takeEvery} from "@redux-saga/core/effects";import {DemoStoreActionEnum, DemoStoreActionInterface} from "@reducers/demoStore";// 延时器const delay = (ms:number) => new Promise(resolve => setTimeout(resolve, ms));function* asyncDemoSaga(action:DemoStoreActionInterface):Generator {    yield call(delay,2000);    yield put({ type: DemoStoreActionEnum.CHANGE,payload:action.payload})}function* watchDemoSaga():Generator {    yield takeEvery(DemoStoreActionEnum.WATCH, asyncDemoSaga)}export default watchDemoSaga;
//redux>sagas>mainSaga.tsximport {all} from "redux-saga/effects"import watchDemoSaga from "@sagas/demo";// saga中间件 主saga,用于区别是否需要saga来处理异步操作,如果没有异步,则放行function* mainSaga() {    yield all([        // 监听 saga 中有 userWatchSaga 操作,所以会拦截这个 action        watchDemoSaga(),    ])}// 主saga要对外暴露出去export default mainSaga;
修改_app.tsx
import type { AppProps } from "next/app";import {ConfigProvider} from "antd";import them from "./antTheme.module.scss";import "@css/iframe.scss";import {useEffect} from "react";import {useDispatch} from "react-redux";import { MobileStoreActionEnum} from "@reducers/mobileStore";import wrapper from "@/redux";import {Dispatch} from "redux";const App = ({ Component, pageProps }: AppProps) => {  const dispatch:Dispatch = useDispatch();  useEffect(():void => {    //判断是哪个客户端(pc,mobile),主要用来兼容样式    if (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {      dispatch({        type:  MobileStoreActionEnum.CHANGE,        payload: true      });      //增加全局class,用于设置全局样式      document.getElementsByTagName('html')[0].className = 'mobile';    }else{      //增加全局class,用于设置全局样式      document.getElementsByTagName('html')[0].className = 'pc';    }  },[])  return  <ConfigProvider theme={{token: them}}>    <Component {...pageProps}/>  </ConfigProvider>}export default wrapper.withRedux(App)
创建redux>index.tsx
import { createWrapper, MakeStore } from "next-redux-wrapper";import { applyMiddleware, createStore, Store} from "redux";import createSagaMiddleware, {SagaMiddleware} from "redux-saga";import { composeWithDevTools } from "redux-devtools-extension/developmentOnly";import rootReducer from "@store/index";import mainSaga from "@sagas/mainSaga"; //异步初始化storeconst makeStore: MakeStore<Store> = () => {    const sagaMiddleware:SagaMiddleware = createSagaMiddleware()    const store:Store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))    sagaMiddleware.run(mainSaga)    return store}export default createWrapper<Store>(makeStore)
封装pc+移动端兼容性button组件,创建兼容性ui框架,需要把antd、antd-mobile二次封装,并合并一些共同的参数,修改成共同的样式;创建非ui框架的组件,只要注意像素单位的兼容就行,如:mobile.module.scss、pc.module.scss,postcss已限制pc.module 样式文件的转换修改.eslintrc.json
{  "extends": "next/core-web-vitals",  "rules": {    "react/display-name": "off"  }}
创建components>antd>button创建pc端button组件button>pc.tsx、button>pc.module.scss
//button>pc.tsx/** * @description pc端Button组件 * *//**********第三方插件、组件引用**********/import React from "react";import {Button as PcButton, ButtonProps} from "antd";import {SizeType} from "antd/es/config-provider/SizeContext";import {ButtonType} from "antd/es/button";/**********当前目录文件*********/import styles from "./pc.module.scss";export interface PcButtonInterface {    type?: ButtonType,    size?: SizeType,    onClick?: ButtonProps['onClick'],    children?: React.ReactNode}const Button = React.memo((props:PcButtonInterface)=>{    return <PcButton className={styles.button}                     type={props.type}                     size={props.size}                     onClick={props.onClick}>        { props.children }    </PcButton >});export default Button;
创建移动端button组件button>mobile.tsx、button>mobile.module.scss
//button>mobile.tsx/** * @description 移动端Button组件 * *//**********第三方插件、组件引用**********/import React from "react";import {Button as MobileButton, ButtonProps} from "antd-mobile";/**********当前目录文件*********/import styles from "./mobile.module.scss";export interface MobileButtonInterface {    type?: ButtonProps['color'],    size?: ButtonProps['size'],    onClick?:ButtonProps['onClick'],    children?: React.ReactNode;}const Button = React.memo((props:MobileButtonInterface)=>{    return  <MobileButton className={styles.button}                          color={props.type}                          size={props.size}                          onClick={props.onClick}>        { props.children }    </MobileButton>});export default Button;
创建兼容pc+移动组件button>index.tsx、button>index.scss
//button>index.tsx/** * @description 同时兼容pc、移动的Button组件 * */import React, {useState} from "react";import PcButton, {PcButtonInterface} from "./pc";import MobileButton, {MobileButtonInterface} from "./mobile";import {useSelector, useStore} from "react-redux";import {Store} from "redux";interface ClientButtonInterface {    type?: PcButtonInterface['type'] & MobileButtonInterface['type'],    size?: PcButtonInterface['size'] & MobileButtonInterface['size'],    onClick?: PcButtonInterface['onClick'] & MobileButtonInterface['onClick'],    children?: React.ReactNode}const Button = React.memo((props: ClientButtonInterface) => {    const store:Store = useStore();    const storeState = store.getState() as any;    const [mobile,setMobile]= useState(storeState.mobileStore)    useSelector((state:any):void => {        if(mobile!=state?.mobileStore){            setMobile(state?.mobileStore);        }    });    return <>        {mobile ? <MobileButton {...props}/> : <PcButton {...props}/>}    </>});export default Button;
//button>index.scss.button{  font-size: 14px;  height: 32px;  padding: 4px 15px;  border-radius: 6px;}
修改button>mobile.module.scss、button>pc.module.scss
@import "./index";
使用
import Button from "@/components/antd/button";<Button type="primary">antd</Button>
持续储存安装依赖,
yarn add redux-persist
修改redux>index.tsx
import { createWrapper, MakeStore } from "next-redux-wrapper";import { applyMiddleware, createStore, Store} from "redux";import createSagaMiddleware, {SagaMiddleware} from "redux-saga";import { composeWithDevTools } from "redux-devtools-extension/developmentOnly";import {persistStore, persistReducer} from "redux-persist";import storage from "redux-persist/lib/storage";import rootReducer from "@store/index";import mainSaga from "@sagas/mainSaga"; //异步初始化store//持久化储存配置const persistConfig = {    key: 'root', //在localStorge中生成key为root的值    storage,    blacklist:['demoSaga'] //设置某个reducer数据不持久化}const makeStore: MakeStore<Store> = () => {    const sagaMiddleware:SagaMiddleware = createSagaMiddleware();    const rootPersistReducer = persistReducer(persistConfig, rootReducer)    const store:Store = createStore(rootPersistReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))    sagaMiddleware.run(mainSaga);    persistStore(store);    return store}export default createWrapper<Store>(makeStore)

五、页面配置

设置页面标题:_app.tsx

import '@/assets/css/globals.scss';import type { AppProps } from 'next/app';import Head from 'next/head';import { ConfigProvider } from 'antd';import them from '@/pages/app.module.scss';export default function App({ Component, pageProps }: AppProps) {  return <>    <Head>      <title>页面标题</title>    </Head>    <ConfigProvider theme={{token: them}}>      <Component {...pageProps}/>    </ConfigProvider>  </>}
设置页面框架代码:_document.tsx,只会在初始化预渲染,设置的内容只会在build后生效
import {Html, Head, Main, NextScript} from 'next/document'export default function Document() {    return (        <Html lang="en">            <Head>                <link rel="icon" href="/favicon.ico"></link>                <meta name="description" content="页面框架"></meta>            </Head>            <body>            <Main/>            <NextScript/>            </body>        </Html>    )}
自定义404页面,pages下新建404.tsx页面
export default function Custom_404(){    return <>404页面</>}

六、图片引用

方法一:原生img,使用' '可能会导致LCP变慢和带宽增加,build时会有此警告

import idCard from '@img/idCard.png';<img src={idCard.src}/>
方法二:使用 next/image;简单的图片引用建议用原生img
//建议用div包括起来,不单独使用,单独使用会自动生成很多自带的样式;Image会自适应div大小import idCard from '@img/idCard.png';import Image from 'next/image';<div><Image src={idCard} alt=""/></div>
next/image 自带优化的api适用于SSR,SSG中无法使用 ,可以改动 next.config.js 配置解决
const nextConfig = {  reactStrictMode: true,  swcMinify: true,  images:{      unoptimized:true  }}module.exports = nextConfig
安装sharp包(高性能图片处理器),Next.js将自动使用它的图像优化
yarn add sharp

七、动态路由

创建pages>demo文件夹创建demo>index.tsx、demo>index.scss、demo>mobile.module.scss、demo>pc.module.scss

//demo>index.tsximport Image from "next/image";import idCard from "@img/idCard.png";import useStyle from "@hook/styleHook";import mobileStyle from "./mobile.module.scss";import pcStyle from "./pc.module.scss";const Demo = () => {    const styles = useStyle(pcStyle,mobileStyle);    return <div className={styles.P_demo}>        <Image src={idCard} alt=""/>    </div>;};export default Demo
//demo>index.scss.P_demo{  img{    width: 100px;    height: 100px;  }}
//demo>mobile.module.scss、demo>pc.module.scss@import "./index";
修改page>index.tsx
import {NextRouter, useRouter} from "next/router";const router:NextRouter = useRouter();<Button onClick={() => router.push('/demo')}>goDemo</Button>
自定义路由,改动 next.config.js 配置
const nextConfig = {  ...  //自定义路由,通常不需要自定义路由,适用于SS  exportPathMap: async ()=>{    return {      '/':{        page:'/index'      }    }  },  ...}module.exports = nextConfig
动态路由修改demo>index.tsx为demo>[id].tsx
import Image from "next/image";import idCard from "@img/idCard.png";import useStyle from "@hook/styleHook";import mobileStyle from "./mobile.module.scss";import pcStyle from "./pc.module.scss";import {NextRouter, useRouter} from "next/router";const Demo = () => {    const styles = useStyle(pcStyle,mobileStyle);    const router:NextRouter = useRouter();    console.log(router.query)    return <div className={styles.P_demo}>        <Image src={idCard} alt=""/>    </div>;};export default Demo
修改pages>index.tsx
<Button onClick={() => router.push('/demo/1')}>goDemo</Button>
路由嵌套[id].tsx:paths 里面参数只有一个id ,/demo/1[...routers].tsx :paths 里面可以包含多个路径,以数组形式发挥参数,/demo/1/3[id]/[comment].tsx:paths 里面可以包含多个路径,以json形式返回,/demo/1/3

八、接口api

NEXT.js存在CSR/SSR/SSG 三种请求方式,最多存在两种:1、CSR+SSR;2、CSR+SSG

CSR请求:常规前端项目中请求方式,由客户端浏览器端发送请求,拿到数据后再渲染道页面

SSR请求:由服务端发起请求(NEXT.js中的node.js),拿到数据后,组装HTML,再把HTML返回到客户端浏览器

SSG请求:与SSR请求类似,由服务端发起请求(NEXT.js中的node.js),拿到数据后,组装HTML,然后静态化输出。由于是完全静态化输出,当数据变化时,必须重新静态化才能更新页面

安装axios

yarn add axios
封装axios
/** * @description axios公共请求封装 * */import axios, {AxiosResponse, InternalAxiosRequestConfig} from "axios";/** * @description 定义相关接口或者枚举 * *///请求枚举export enum MethodEnum {    Get='GET',    Post='POST'}//返回结果export interface ResponseResultInterface<Body> {    Header:{},    Body: Body}//请求参数export interface RequestInterface<params> {    url:string,    params?:params,    method?:MethodEnum}/** * 封装axios * */// 添加请求拦截器axios.interceptors.request.use( (config:InternalAxiosRequestConfig)=>{    return config;}, (error)=>{    return Promise.reject(error);});// 添加响应拦截器axios.interceptors.response.use( (response:AxiosResponse) => {    return response;}, (error) => {    return Promise.reject(error);});/** * @method useAxiosRequest axios请求封装 * @param requestPar { RequestInterface } 请求url * @return Promise * */const baseRequest= async <params,responseData>(requestPar:RequestInterface<params>):Promise<responseData> => {    const requestResult:AxiosResponse = await axios({        method: requestPar.method || MethodEnum.Post,        url: requestPar.url,        data: requestPar.params    });    return requestResult.data as responseData;};export default baseRequest;
配置引入路径,修改 tsconfig.json
"paths": {     ...      "@common/*": [          "./src/common/*"     ],     "@api/*": [           "./src/pages/api/*"     ],     ...}
创建api服务,pages>api>demoApi.tsx
// Next.js API route support:  type { NextApiRequest, NextApiResponse } from "next";import {ResponseResultInterface} from "@common/baseRequest";export interface DemoInterface {    id: number,    name?: string}type ApiDemoType = ResponseResultInterface<DemoInterface>export default function demoApi(    req: NextApiRequest,    res: NextApiResponse<ApiDemoType>):void {    let data:ApiDemoType= {        Header:{},        Body:{            id:-1        }    };    if(req.method == "GET"){        const id:number = Number(req.query.id);        data.Body.id = id;        switch (id) {            case 1:                data.Body.name = "我是API服务1"                break;        }        res.status(200).json(data)    }else{        res.status(200).json(data)    }}
修改pages>demo>[id].tsx
import Image from "next/image";import {NextRouter, useRouter} from "next/router";import {GetServerSideProps} from "next";import {ParsedUrlQuery} from "querystring";import idCard from "@img/idCard.png";import useStyle from "@hook/styleHook";import baseRequest, {MethodEnum, RequestInterface} from "@common/baseRequest";import {DemoInterface} from "@api/demoApi";import mobileStyle from "./mobile.module.scss";import pcStyle from "./pc.module.scss";const Demo = (props: DemoInterface) => {    const styles = useStyle(pcStyle,mobileStyle);    const router:NextRouter = useRouter();    console.log(router.query)    console.log(props);    return <div className={styles.P_demo}>        <Image src={idCard} alt=""/>    </div>;};/** * getServerSideProps 适用于SSR,不适用于SSG * getStaticProps SSR 和 SSG 均支持,但仅在网站build时候发起API请求 * getServerSideProps 和 getStaticProps请求都是在服务端进行不涉及跨域 * */export const getServerSideProps: GetServerSideProps = async (paths) => {    const query:ParsedUrlQuery = paths.query;    const requestOption:RequestInterface<undefined>={        url:`{query.id}`,        method:MethodEnum.Get    }    const requestResult = await baseRequest<DemoInterface>({        url: requestOption.url,        method:requestOption.method    });    return {        props: requestResult.Body    }}/** * SSG 静态生成 * getStaticPaths build 时会生成多个页面 * 只是用于getStaticProps * */// export const getStaticPaths: GetStaticPaths<DemoParams> = async () => {//     // const arr: string[] = ['1', '2'];//     // const paths = arr.map((id) => {//     //     return {//     //         params: { id },//     //     }//     // })//     // return {//     //     //这里的路由参数提供给getStaticProps使用//     //     paths,//     //     //不在以上参数路由将返回404//     //     fallback: false//     // }//     const id1:DemoParams = {id: '1'};//     const id2:DemoParams = {id: '2'};//     const staticPathOption = {//         //这里的路由参数提供给getStaticProps使用//         path: [{//             params: id1//         }, {//             params: id2//         }],//         //不在以上参数路由将返回404dc//         // fallback: false//     }//     return staticPathOption// }export default Demo

九、生成静态网站(SSG)

设置SSG的export命令,修改package.json

"scripts": {  "dev": "next dev",  "build": "next build && next export",  "start": "next start",  "lint": "next lint"},
然后执行yarn build,该命令回显执行next build 再执行 next export,输出目录为根目录下的out目录设置静态资源的bassePath,如果发布在服务器二级目录需要设置,更改next.config.js 配置;/app为二级目录
const nextConfig = {  reactStrictMode: true,  swcMinify: true,  basePath: process.env.NODE_ENV == "development"? '':'/app'  images:{      unoptimized:true  }}module.exports = nextConfig
设置输出目录export输出目录为app
"scripts": {  "dev": "next dev",  "build": "next build && next export -o app",  "start": "next start",  "lint": "next lint"},

十、以SSR模式运行项目

yarn buildyarn start -p 4000 //默认端口3000

十一、动态生成项目目录

安装依赖

npm install cross-env -g
package.json
 "scripts": {    "dev": "next dev",    "build": "next build && next export",    "start": "next start",    "lint": "next lint",    "customBuild": "cross-env BASE_PSTH=%npm_config_base% next build && next export -0 %npm_config_base%",    "customBuild": "cross-env BASE_PSTH=$npm_config_base next build && next export -0 $npm_config_base%"//mac  },

运行npm run customBuild --base=/demo --out=demo

十二、多环境开发配置

安装依赖

yarn add cross-env --dev
根目录下创建.env.production(生产环境)、.env.development(开发环境)、.env.test(测试环境)、.env.local(默认环境,始终覆盖默认设置)、.env(所有环境)
//.env.testTEST=test //只有服务端可以获取到NEXT_PUBLIC_HOST= //变量暴漏给浏览器端,加NEXT_PUBLIC_
指定环境运行,修改package.json
  "scripts": {    "dev:test": "cross-env NODE_ENV=test next dev",    },    页面打印:  console.log(process.env.TEST);  console.log(process.env.NEXT_PUBLIC_HOST);

十二、项目部署

安装nginx:,下载合适的版本,解压到任意位置nginx 配置启动cd到nginx目录运行 nginx.exe启动cd到nginx目录运行start nginx,访问 正常访问nginx运行成功启动cd到nginx目录运行重新加载配置启动cd到nginx目录运行nginx -s stop 停止启动cd到nginx目录运行nginx -s quit 正常关闭启动cd到nginx目录运行nginx -s reopen 重新打开配置nginx.conf

 server {      listen       9001;      server_name  localhost;      # server_name  btyhub.site, ;      # ssl两个文件,放在 nginx的conf目录中      # ssl_certificate      btyhub.site_bundle.pem;      # ssl_certificate_key  btyhub.site.key;      # ssl_session_cache    shared:SSL:1m;      # ssl_session_timeout  5m;      # ssl_ciphers  HIGH:!aNULL:!MD5;      # ssl_prefer_server_ciphers  on;      # 代理到Next的服务,默认3000端口,也可以在start的时候指定      location / {          proxy_pass    ;      }  }
安装 pm2,node进程管理工具:npm install -g pm2把打包后得.next,next.config.js 上传服务器package.json,运行yarn install
{  "name": "react_common",  "version": "0.1.0",  "private": true,  "scripts": {    "start": "next start"  },  "dependencies": {   //项目下package.json 中dependencies  },  "devDependencies": {     //项目下package.json 中devDependencies    "child_process": "^1.0.2"}
安装child_process,运行yarn add child_process --dev,创建start.js
let exec = require("child_process").exec;//yarn start -p 9003 指定端口运行项目exec("yarn start", {windowsHide: true});
运行pm2 start start.js --name projectName,pm2:node进程管理工具

十三、其他

next 命令

yarn dev --help 某个命令帮助信息next lint 设置 Next.js 的内置 ESLint 配置next lint --no-cache 清除 ESLint 缓存
next内置组件import Head from 'next/head'import Image from 'next/image'import { Inter } from 'next/font/google'import { Html, Head, Main, NextScript } from 'next/document'import type { AppProps } from 'next/app'import type { NextApiRequest, NextApiResponse } from 'next'import { useRouter } from 'next/router'import Link from 'next/link'import Script from 'next/script'import { useAmp } from 'next/amp'import type { NextRequest } from 'next/server'import { Inter } from 'next/font/google'import getConfig from 'next/config'next.config.js
const configs = {  // 编译文件的输出目录  distDir: 'dest',  // 是否给每个路由生成Etag  generateEtags: true,  // 页面内容缓存配置  onDemandEntries: {    // 内容在内存中缓存的时长(ms)    maxInactiveAge: 25 * 1000,    // 同时缓存多少个页面    pagesBufferLength: 2,  },  // 在pages目录下那种后缀的文件会被认为是页面  pageExtensions: ['jsx', 'js'],  // 配置buildId  generateBuildId: async () => {    if (process.env.YOUR_BUILD_ID) {      return process.env.YOUR_BUILD_ID    }    // 返回null使用默认的unique id    return null  },  // 手动修改webpack config  webpack(config, options) {    return config  },  // 修改webpackDevMiddleware配置  webpackDevMiddleware: config => {    return config  },  // 可以在页面上通过 procsess.env.customKey 获取 value  env: {    customKey: 'value',  },  //CDN 前缀支持  assetPrefix: ';,  //静态优化指标  devIndicators: {      autoPrerender: false  },  //禁止etag生成  generateEtags: false,  //禁止x-powered-by,x-powered-by用于告知网站是用何种语言或框架编写的  poweredByHeader: false  //自定义路由  exportPathMap: async ()=>{    return {        '/':{              page:'/index'            }    }  },  images:{      unoptimized:true  },  // 只有在服务端渲染时才会获取的配置  serverRuntimeConfig: {    mySecret: 'secret',    secondSecret: process.env.SECOND_SECRET,  },  // 在服务端渲染和客户端渲染都可获取的配置  publicRuntimeConfig: {    staticFolder: '/static',  },  }

标签: #复制assets文件