百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

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

myzbx 2025-03-25 15:41 23 浏览

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

  1. antd官网:https://ant.design/components/overview-cn/
  2. antd-mobile官网:https://mobile.ant.design/zh
  3. next.js: https://www.nextjs.cn/
  4. react:https://react.zcopy.site/
  5. redux:https://react-redux.js.org/api/hooks#custom-context

一、介绍

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

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

二、构建项目

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

三、调整项目

  1. 文件目录

  1. _app.tsx
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return 
}
  1. index.tsx
import {NextPage} from "next";

const Home: NextPage = (props) => {  
    return 
dsada
}; export default Home

三、静态资源assets

  1. 创建assets>css、assets>font、assets>img
  2. 安装依赖
yarn add sass
  1. 集成字体图标,下载阿里icon库,解压后把压缩包里的文件复制到assets>font
  1. 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 同一浏览器样式,下载后放入该文件中
http://nicolasgallagher.com/about-normalize-css/
https://github.com/necolas/normalize.css
//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字体大小
  1. 配置引入路径tsconfig.json
"paths": {
    ...
    "@css/": [
        "./src/assets/css/"
    ],
    "@img/": [
        "./src/assets/img/"
    ],
    ...
}
  1. 引入全局样式,修改_app.tsx
import type { AppProps } from "next/app";
import "@css/iframe.scss";

export default function App({ Component, pageProps }: AppProps) {
  return 
}
  1. 引入全局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
  1. 配置antd-mobile主题,https://mobile.ant.design/zh/guide/theming,新建 css>antdMobileTheme.scss
:root:root {
  --adm-color-primary: #ff4d4f;
}
  1. 配置antd主题
  • 新建pages>antTheme.module.scss

/* antd 主题配置
 * 详细配置可参考 https://ant.design/docs/react/customize-theme-cn*/

: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  
    
  
}
  1. 集成postcss
  • 安装依赖postcss-px-to-viewport-8-plugin
yarn add postcss-px-to-viewport-8-plugin --dev
  • 根目录新建postcss.config.js
//postcss.config.js

module.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

  1. 安装依赖
yarn add redux react-redux redux-saga
yarn add @types/react-redux @types/redux-saga next-redux-wrapper redux-devtools-extension --dev
  1. 创建redux>reducers、redux>sagas文件夹
  2. 配置引入路径tsconfig.json
"paths": {
    ...
    "@reducers/*": [
      "./src/redux/store/reducers/*"
    ],
    "@sagas/*": [
      "./src/redux/store/sagas/*"
    ],
    "@store/*": [
      "./src/redux/store/*"
    ],
    ...
}
  1. 创建第一个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;
  1. 创建第一个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;
  1. 依次创建redux>sagas>demo.tsx、redux>sagas>mainSaga.tsx
  • saga的应用场景是复杂异步,如长时事务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取消,则返回true
throttle:节流
//redux>sagas>demo.tsx
import {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.tsx

import {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;
  1. 修改_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  
    
  
}

export default wrapper.withRedux(App)
  1. 创建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"; //异步初始化store

const makeStore: MakeStore = () => {
    const sagaMiddleware:SagaMiddleware = createSagaMiddleware()
    const store:Store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))
    sagaMiddleware.run(mainSaga)
    return store
}

export default createWrapper(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 
            { props.children }
        
    });
    
    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  
            { props.children }
        
    });
    
    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 ?  : }
        
    });
    
    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";
    
    
    1. 持续储存
    • 安装依赖,https://www.npmjs.com/package/redux-persist
    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 = () => {
        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(makeStore)

    五、页面配置

    1. 设置页面标题:_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 <>
        
          页面标题
        
        
          
        
      
    }
    1. 设置页面框架代码:_document.tsx,只会在初始化预渲染,设置的内容只会在build后生效
    import {Html, Head, Main, NextScript} from 'next/document'
    
    export default function Document() {
        return (
            
                
                    
                    
                
                
                
    ) }
    1. 自定义404页面,pages下新建404.tsx页面
    export default function Custom_404(){
        return <>404页面
    }

    六、图片引用

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

    七、动态路由

    1. 创建pages>demo文件夹
    2. 创建demo>index.tsx、demo>index.scss、demo>mobile.module.scss、demo>pc.module.scss
    //demo>index.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";
    
    const Demo = () => {
        const styles = useStyle(pcStyle,mobileStyle);
        return 
    ; }; export default Demo
    //demo>index.scss
    
    .P_demo{
      img{
        width: 100px;
        height: 100px;
      }
    }
    //demo>mobile.module.scss、demo>pc.module.scss
    @import "./index";
    1. 修改page>index.tsx
    import {NextRouter, useRouter} from "next/router";
    
    const router:NextRouter = useRouter();
    
    1. 自定义路由,改动 next.config.js 配置
    const nextConfig = {
      ...
      //自定义路由,通常不需要自定义路由,适用于SS
      exportPathMap: async ()=>{
        return {
          '/':{
            page:'/index'
          }
        }
      },
      ...
    }
    
    
    module.exports = nextConfig
    1. 动态路由
    • 修改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 
    ; }; export default Demo
    • 修改pages>index.tsx
    1. 路由嵌套
    • [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,然后静态化输出。由于是完全静态化输出,当数据变化时,必须重新静态化才能更新页面

    1. 安装axios
    yarn add axios
    1. 封装axios
    /**
     * @description axios公共请求封装
     * */
    import axios, {AxiosResponse, InternalAxiosRequestConfig} from "axios";
    
    /**
     * @description 定义相关接口或者枚举
     * */
    
    //请求枚举
    export enum MethodEnum {
        Get='GET',
        Post='POST'
    }
    
    //返回结果
    export interface ResponseResultInterface {
        Header:{},
        Body: Body
    }
    
    //请求参数
    export interface RequestInterface {
        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 (requestPar:RequestInterface):Promise => {
        const requestResult:AxiosResponse = await axios({
            method: requestPar.method || MethodEnum.Post,
            url: requestPar.url,
            data: requestPar.params
        });
        return requestResult.data as responseData;
    };
    
    export default baseRequest;
    
    1. 配置引入路径,修改 tsconfig.json
    "paths": {
         ... 
         "@common/*": [
              "./src/common/*"
         ],
         "@api/*": [  
             "./src/pages/api/*"
         ],
         ...
    }
    1. 创建api服务,pages>api>demoApi.tsx
    // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
    import type { NextApiRequest, NextApiResponse } from "next";
    import {ResponseResultInterface} from "@common/baseRequest";
    
    export interface DemoInterface {
        id: number,
        name?: string
    }
    
    type ApiDemoType = ResponseResultInterface
    
    export default function demoApi(
        req: NextApiRequest,
        res: NextApiResponse
    ):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)
        }
    }
    1. 修改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 
    ; }; /** * getServerSideProps 适用于SSR,不适用于SSG * getStaticProps SSR 和 SSG 均支持,但仅在网站build时候发起API请求 * getServerSideProps 和 getStaticProps请求都是在服务端进行不涉及跨域 * */ export const getServerSideProps: GetServerSideProps = async (paths) => { const query:ParsedUrlQuery = paths.query; const requestOption:RequestInterface={ url:`http://localhost:3000/api/demoApi?id=${query.id}`, method:MethodEnum.Get } const requestResult = await baseRequest({ url: requestOption.url, method:requestOption.method }); return { props: requestResult.Body } } /** * SSG 静态生成 * getStaticPaths build 时会生成多个页面 * 只是用于getStaticProps * */ // export const getStaticPaths: GetStaticPaths = 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)

    1. 设置SSG的export命令,修改package.json
    "scripts": {
      "dev": "next dev",
      "build": "next build && next export",
      "start": "next start",
      "lint": "next lint"
    },
    1. 然后执行yarn build,该命令回显执行next build 再执行 next export,输出目录为根目录下的out目录

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

    十、以SSR模式运行项目

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

    十一、动态生成项目目录

    1. 安装依赖
    npm install cross-env -g
    1. 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
      },


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

    十二、多环境开发配置

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


    十二、项目部署

    1. 安装nginx:https://nginx.org/en/download.html,下载合适的版本,解压到任意位置nginx 配置
    • 启动cd到nginx目录运行 nginx.exe
    • 启动cd到nginx目录运行start nginx,访问http://localhost:80 正常访问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, www.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    http://127.0.0.1:3000/;
          }
    
      }
    1. 安装 pm2,node进程管理工具:npm install -g pm2
    2. 把打包后得.next,next.config.js 上传服务器
    3. 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"
    }
    
    1. 安装child_process,运行yarn add child_process --dev,创建start.js
    let exec = require("child_process").exec;
    //yarn start -p 9003 指定端口运行项目
    exec("yarn start", {windowsHide: true});
    1. 运行pm2 start start.js --name projectName,pm2:node进程管理工具

    十三、其他

    1. next 命令
    yarn dev --help 某个命令帮助信息
    next lint 设置 Next.js 的内置 ESLint 配置
    next lint --no-cache 清除 ESLint 缓存
    1. 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'
    1. 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: 'https://cdn.mydomain.com',
      //静态优化指标
      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',
      },  
    }

    相关推荐

    Luminati代理动态IP教程指南配置代理VMLogin中文版反指纹浏览器

    介绍如何使用在VMLogin中文版设置Luminati代理。首先下载VMLogin中文版反指纹浏览器(https://cn.vmlogin.com)对于刚接触Luminati动态ip的朋友,是不是不懂...

    文档中图形及子图形的处理(word中的图形对象有何特点)

    【分享成果,随喜正能量】走得越远,见识越多,认识的人越多,你就越能体会到,人这一辈子,你真的在意的,同时又在意你的人,就那么几个,这几个人,就是你全部的世界。三两知己,爱人在侧,父母康健,听起来平淡无...

    Python爬虫破解滑动验证码教程(python绕过滑动验证码)

    破解滑动验证码通常需要结合图像识别和模拟人类操作,以下是分步骤的解决方案:1.分析验证码类型缺口识别型:背景图带缺口,滑块图带凸块轨迹验证型:除了位置还需模拟人类移动轨迹2.获取验证码图片方法一:...

    「教程」5 分钟带你入门 kivy(新手kp教学)

    原创:星安果AirPythonkivy语言通过编写界面UI,然后利用Python定义一些业务逻辑,可以移植很多功能模块到移动端直接执行。下面对kivy常见用法做一个汇总。1、什么是...

    比呀比: Fossil Estate Canvas EW 男式复古邮差包 $70.99

    Fossil是一个来自美国的全球性生活时尚品牌,始建于1984年,专注于时尚配件,是第一个将手表的价值与款式完美结合的美国品牌,如今Fossil已跃身成为美国最受欢迎的品牌之一。这款FossilE...

    智能教学:如何在网上授课(网上授课怎么弄)

    摘要:因为担心传统课堂可能会传播冠状病毒,许多大学已经开始在网上授课。耶鲁-新加坡国立大学的讲师凯瑟琳·谢伊·桑格(CatherineSheaSanger)解释了如何快速而有效地做到这一点。当新型冠...

    wxPython库教程系列之图片:托盘图标和图片缩放、移动

    1概要:=====1.1托盘图标设置1.2普通图片显示:原图显示,缩放显示,窗口与图片大小相互适应。1.3按钮图片设置1.4移动图片和zoom菜单按钮联动设置2托盘图标:========2...

    UE4渲染目标开发教程(ue4渲染效果图质量怎么样)

    渲染目标(RenderTarget)是你可以在运行时写入的纹理。在引擎方面,它们存储基础颜色、法线和环境光遮蔽等信息。在用户方面,渲染目标主要用作一种辅助相机。你可以将场景捕捉指向某物并将图像存储到...

    比呀比: Fossil 化石 Canvas NS 男士复古帆布斜挎包 $57.59

    FossilCanvasNS男士复古帆布斜挎包,尺寸约为26.5*11*33厘米。采用100%纯棉帆布面料,融合了休闲与百搭的外形,在经典的款型呈现复古质感。内设1个拉链袋,2个搭扣数码产品袋和...

    比呀比: Timberland 添柏岚 Canvas Cord Case 帆布旅行手包 $5.99

    Timberland添柏岚这款耐用帆布旅行手包,虽然一眼过去,觉得不咋地,但是品牌和质量还是妥妥滴,非常适合装一些零零碎碎的小东西,便于携带,多色可选,重点是价格更是感动价啊。目前这款包在6pm报价...

    提炼文章/知识资料,两键转换成小红书图片

    现在AI的功能已经越来越强大了,通过AI可以提高我们不少工作效率。刚好前几天做了一个几乎“一气呵成”,把长文章转成小红书卡片的流程Demo,分享给大家。之前发过两篇利用AI把长文章转成小红书图片...

    python海龟绘图turtle(一):画布和窗体

    海龟绘图(turtle)是python的一个有趣的内置模块,是python语言的标准库之一,是入门级的图形绘制函数库。海龟绘图(turtle)可以根据编写的控制指令(代码),让一个小“海龟”在屏幕上来...

    在文档中添加画布及图片(word中如何添加画布)

    【分享成果,随喜正能量】宁可正而不足,不可邪而有余。相识满天下,知心能几人。书七成,戏三分,牛皮灯影胡编成。布施不如还债,修福不如避祸。勿以恶小而为之,勿以善小而不为。。《VBA之Word应用》,是我...

    知识管理神器 Obsidian,终于有了白板功能!

    沙牛提示阅读本文需要3分钟,Obsidian白板功能来了!如果你喜欢本文,就分享给你的小伙伴!01白板继双链笔记之后,这一年,白板类工具开始火了起来。顾名思义,白板类工具,它给了你一张无限尺寸...

    虚拟背景第一弹!教你如何在家中优雅地“学在交大”!

    交大将于3月2日正式开始线上教学(3月1日举行线上教学第一课|视频直播课)目前正处于网课试课阶段交大在线课程教学以ZOOM、Canvas等作为主平台平台的虚拟背景功能可以具特别的环境效果更好地沉浸课堂...