美文网首页
Nextjs之app router应用

Nextjs之app router应用

作者: 说叁两事 | 来源:发表于2024-06-30 20:55 被阅读0次

    项目初始化

    1. 创建项目
    npx create-next-app@latest
    

    初始选择以下配置

    What is your project named? my-app
    Would you like to use TypeScript? No / Yes
    Would you like to use ESLint? No / Yes
    Would you like to use Tailwind CSS? No / Yes
    Would you like to use `src/` directory? No / Yes
    Would you like to use App Router? (recommended) No / Yes
    Would you like to customize the default import alias (@/*)? No / Yes
    
    1. 创建.editorconfig

    统一代码风格

    # top-most EditorConfig file
    root = true
    
    # 针对所有文件
    [*]
    indent_style = space
    indent_size = 2
    charset = utf-8
    trim_trailing_whitespace = true
    insert_final_newline = true
    
    1. 创建.vscode/settings.json

    针对vscode配置,提升vscode开发效率

    {
        "editor.tabSize": 2,
        "typescript.inlayHints.parameterNames.enabled": "all",
        "typescript.inlayHints.parameterTypes.enabled": true,
        "typescript.inlayHints.variableTypes.enabled": true,
        "typescript.inlayHints.propertyDeclarationTypes.enabled": true,
        "typescript.inlayHints.enumMemberValues.enabled": true,
        "typescript.inlayHints.functionLikeReturnTypes.enabled": true
      }
    

    多端适配

    多端适配采用的是tailwindcss方案

    1. 安装tailwindcss
    npm i -D tailwindcss postcss autoprefixer
    npx tailwindcss init
    

    该操作会自动以下操作:

    • 更新package.json

    • 创建tailwind.config.js

    1. 更新tailwind.config.js配置
    import type { Config } from 'tailwindcss';
    
    const config: Config = {
      content: [ // 配置tailwindcss的作用范围:使用tailwindcss的地方
        './app/**/*.{js,ts,jsx,tsx,mdx}',
        './components/**/*.{js,ts,jsx,tsx,mdx}'
      ],
      theme: {
        extend: { }
      },
      plugins: []
    };
    export default config;
    
    1. 引入tailwindcss内置样式组件
    /* app/global.css */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    1. 创建postcss.config.js文件
    module.exports = {
      plugins: {
        tailwindcss: {},
        autoprefixer: {},
      }
    }
    
    
    1. 测试可用性
    // 更新app/page.tsx
    export default function Home() {
      return (
        <main>
          <div className="text-3xl text-white bg-black">22222</div>
        </main>
      );
    }
    
    1. 特定断点配置
    // tailwind.config.ts
    const config: Config = {
      content: [
        './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
        './src/components/**/*.{js,ts,jsx,tsx,mdx}',
        './src/app/**/*.{js,ts,jsx,tsx,mdx}'
      ],
      screens: {
        xl: { min: '1281px' }, // pc
        lg: { max: '1280px' }, // pad
        md: { max: '760px' }, // 折叠屏
        sm: { max: '450px' } // 手机
      },
      plugins: []
    };
    export default config;
    

    Caveat:

    screens的配置是有优先级的,上述配置是大屏优先,后续的media样式会覆盖先序的media匹配:sm > md > lg > xl。

    移动端适配

    rem + tailwindcss

    移动端适配采用flexiblejs的rem方案

    • tailwindcss单位为remhtml#fontsize以浏览器默认字号为基准(通用为16px)

    • 移动端rem基础由设计图&设备宽度确定,是动态的

    1. 定制化tailwindcss

    根据设计稿规范定制化tailwindcss,单位为px,后续由插件自动转换。

    import type { Config } from 'tailwindcss';
    
    const config: Config = {
      content: [
        './components/**/*.{js,ts,jsx,tsx,mdx}',
        './app/**/*.{js,ts,jsx,tsx,mdx}',
      ],
      theme: {
        fontSize: {
          xs: '12px',
          sm: '14px',
          base: '16px',
          lg: '18px',
          xl: '20px',
          '2xl': '24px',
          '3xl': '30px',
          '4xl': '36px',
          '5xl': '48px',
          '6xl': '60px',
          '7xl': '72px',
        },
        spacing: {
          px: '1px',
          0: '0',
          0.5: '2px',
          1: '4px',
          1.5: '6px',
          2: '8px',
          2.5: '10px',
          3: '12px',
          3.5: '14px',
          4: '16px',
          5: '20px',
          6: '24px',
          7: '28px',
          8: '32px',
          9: '36px',
          10: '40px',
          11: '44px',
          12: '48px',
          14: '56px',
          16: '64px',
          20: '80px',
          24: '96px',
          28: '112px',
          32: '128px',
          36: '144px',
          40: '160px',
          44: '176px',
          48: '192px',
          52: '208px',
          56: '224px',
          60: '240px',
          64: '256px',
          72: '288px',
          80: '320px',
          96: '384px',
        },
        extend: {
          colors: { // colors在className中使用,才会被打包;否则,自定义颜色不起作用;
            "whiteFix": "white"
          }
          lineHeight: {
            3: '12px',
            4: '16px',
            5: '20px',
            6: '24px',
            7: '28px',
            8: '32px',
            9: '36px',
            10: '40px',
          },
          backgroundImage: {
            'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
            'gradient-conic':
              'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
          },
        },
      },
      plugins: [],
    };
    export default config;
    
    
    • colors在className中使用,才会被打包;否则,自定义颜色不起作用;
    1. 安装postcss-pxtorem插件

    自动将样式中的px单位转换为rem单位,可通过设置PX(单位大写)禁止转换。

    npm i -D postcss-pxtorem
    
    1. 配置postcss.config.js
    module.exports = {
      plugins: {
        tailwindcss: {},
        autoprefixer: {},
        'postcss-pxtorem': {      
            rootValue: 392 / 10,  //根据设计图    
            unitPrecision: 5,      
            propList: ['*'],      
            selectorBlackList: [/^\.html/],      
            exclude: /(node_module)/,      
            replace: true,      
            mediaQuery: false,      
            minPixelValue: 0,    
        }
      }
    }
    
    1. 引入[flexible.js](https://github.com/amfe/lib-flexible/tree/master)
        <html lang="en">
          <head>
            <script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.2/??flexible_css.js,flexible.js"></script>
          </head>
          <body>{children}</body>
        </html>
    

    FAQs:

    • 引入flexible.js为什么不使用next/script内置<Script>标签,而使用<script>

    next/script内置<Script>标签针对脚本加载进行了优化,采用的是异步加载;

    • 异步加载flexible.js的话,ssr页面初始化加载时html标签采用浏览器默认的样式,没有指定fontsize样式;

    • 加载flexible.js执行阶段,依据客户端deviceWidth动态计算html#fontsize,引发页面整体样式的变化,浏览器会重新绘制页面,导致页面闪屏;

    <script>是同步加载,会阻塞DOM树的渲染;

    • 同步加载flexible.js的话,ssr页面渲染之前flexible已经加载执行完毕,不会出现页面重新渲染而导致的闪屏。
    • 大字体情况下,flexible.js会受到影响,布局变大

    禁止大字体,用户修改手机字号时页面不受影响

    function rectifyNonstandardFontSize() {
      var $dom = document.createElement('div');
      $dom.style = 'font-size:20px;';
      document.body.appendChild($dom);
      var scaledFontSize = parseInt(
        window.getComputedStyle($dom, null).getPropertyValue('font-size')
      );
      document.body.removeChild($dom);
      var scaleFactor = 20 / scaledFontSize;
      var originRootFontSize = parseInt(
        window
          .getComputedStyle(document.documentElement, null)
          .getPropertyValue('font-size')
      );
      document.documentElement.style.fontSize =
        originRootFontSize * scaleFactor * scaleFactor + 'px';
    }
    rectifyNonstandardFontSize();
    
    • windows环境下,部分场景下flexible导致布局出现问题,代码报错:

    使用flexible必须判断client环境,使用typeof window !== 'undefined'

    • tailwindcss在IE上无法使用

    一般来说,Tailwind CSS v3.0专为Chrome、Firefox、Edge和Safari的最新稳定版本而设计,并在这些浏览器上进行了测试。它不支持任何版本的 IE,包括 IE 11,支持Edge。

    若需要支持IE,可以使用Tailwind CSS v1.9,具体支持哪些浏览器完全取决于样式的使用,而不是框架。

    • 若使用 Tailwind 的 Flexbox 工具构建的网格,它只能在 IE10 以上版本中运行,因为IE9 不支持 Flexbox;
        <div class="flex">
          <div class="w-1/3"><!-- ... --></div>
          <div class="w-1/3"><!-- ... --></div>
          <div class="w-1/3"><!-- ... --></div>
        </div>
    
    • 如果需要支持 IE9,可以使用浮点来构建网格,因为几乎所有浏览器都支持浮点;
    <div class="clearfix">
      <div class="float-left w-1/3"><!-- ... --></div>
      <div class="float-left w-1/3"><!-- ... --></div>
      <div class="float-left w-1/3"><!-- ... --></div>
    </div>
    
    • tailwindcss不会自动为其任何样式添加供应商前缀,需要手动添加自动前缀器。
    # Using npm
    npm install autoprefixer
    
    # Using Yarn
    yarn add autoprefixer
    
    // postcss.config.js
    module.exports = {
      plugins: [
        require('tailwindcss'),
        require('autoprefixer'),
      ]
    }
    

    多主题

    根据条件定义主题

    const adaptMultipleThemes = () => {
      const isDark = getCookie('darkMode') === 'true'
      isDark && document && document.documentElement.classList.add('isDark')
    }
    

    scss声明主题样式

    // scss主题设置
    $linkColor: #3482FF!default;
    $linkDarkColor: red;
    @mixin mixDarkTheme () {
      & {
        @content;
      }
      @at-root :global(.isDark) { // :global脱离css module
        $linkColor: $linkDarkColor!global;
      }
      @at-root :global(.isDark) & {
        @content;
      };
    }
    

    主题样式应用

    1. 在入口文件中调用adaptMultipleThemes

    2. 在样式中使用@include mixDarkTheme

    .title {
      @include mixDarkTheme(){
        color: $linkColor;
      }
    }
    

    站点配置

    每个Page独立配置title

    1. 给每个页面增加layout.js

    2. layout.js中配置metadata

    拆分next.config.mjs

    next.config.js默认仅支持commonjs规范,若使用ES Module规范,更改后缀名为.mjs

    .mjs配置为例进行拆分:

    1. 拆分需要使用.mjs后缀文件

    2. .mjs文件使用ES Module格式书写

    3. 引入需要携带.mjs后缀

    全局依赖

    在Next.js中通过<Script>引入像Swiper这种全局变量,在使用时会报Swiper is not defined这种错误。

    1. 定义类型声明文件
    // next-env.d.ts
    declare global {
      var Swiper: any;
      interface Window {
        Swiper: any
      }
    }
    declare interface Window {
      Swiper: any
    }
    
    
    1. 在使用全局变量的地方引入类型声明文件
    import { Swiper } from '@/types/globals';
    // 底屏swiper
    useEffect(() => {
      const myswiper = new Swiper('.swiper-container', {
        slidesPerView: 1.05,
        spaceBetween: 16
      });
      return () => {
        myswiper.destroy(true);
      };
    }, []);
    

    环境变量

    CLI

    1. 通过命令行配置运行环境
    // package.json
      "scripts": {
        "dev": "next dev",
        "build": "cross-env RUN_ENV=production next build && cross-env RUN_ENV=production npm run gennginx",
        "build:dev": "cross-env RUN_ENV=staging next build && cross-env RUN_ENV=staging npm run gennginx",
      },
    
    1. next.config.js#env读取环境变量写入代码

      • process.env.*只能node环境使用;

      • next.config.js#env会直接打入包内,serverclient都可以使用;

    
    import Analyzer from '@next/bundle-analyzer';
    import withImages from 'next-images';
    
    import { domains } from './constants/domains.mjs';
    
    const withBundleAnalyzer = Analyzer({
      enabled: process.env.ANALYZE === 'true'
    });
    
    const nextConfig = withImages(
      withBundleAnalyzer({
        // output: 'export', // 开启CSR,需要去除rewrite配置
        assetPrefix:
          process.env.RUN_ENV == void 0
            ? domains.STATIC_URL_PREFIX
            : `${domains.STATIC_URL_PREFIX}${domains.ASSET_PATH_PRFIX}`, // 静态资源前缀
        images: {
          loader: 'custom',
          loaderFile: './src/image-loader/index.mjs'
        },
        env: {
          RUN_ENV: process.env.RUN_ENV
        },
        webpack: (config) => {
          // 可自定义webpack配置
          return config;
        },
      })
    );
    
    export default nextConfig;
    

    Caveat:

    • next.config.mjs引入自定义模块只能是.mjs文件,无法访问*.ts文件,会报错。

    • next.config.mjs#env配置变量的访问限制:

      • 不能解构:const { RUN_ENV } = process.env;

      • 不能使用变量:const env = process.env[$var];

    1. 通过process.env.*声明环境配置
    // ./constants/domains.mjs
    const dev = {
      HOST: '',
      STATIC_URL_PREFIX: '',
      ASSET_PATH_PRFIX: '/assets',
    };
    const staging = {
      HOST: 'https://<staging.host>.com',
      STATIC_URL_PREFIX: 'https://<staging.static.host.com>',
      ASSET_PATH_PRFIX: '/assets',
    };
    const prod = {
      HOST: 'https://<prod.host>.com',
      STATIC_URL_PREFIX: 'https://<prod.static.host.com>',
      ASSET_PATH_PRFIX: '/assets',
    };
    const env = process.env.RUN_ENV;
    function getDomains() {
      switch (env) {
        case 'production':
          return prod;
        case 'staging':
          return staging;
        default:
          return dev;
      }
    }
    const domains = getDomains();
    export { domains };
    

    Good to know:

    • 借助import ... from ...的缓存特性,多次引用,只有第一次引用会执行代码,后续使用的是对象引用,而非拷贝。
    1. 消费者调用
    // app/page.tsx
    import { domains } from '@/constants/domains.mjs'; //必须携带mjs后缀
    console.log(domains.Host)
    

    Good to knows

    .env文件

    • 带Next_Public_开头的变量,服务端、客户端都可以访问;

    • 不带Next_Public_开头的变量,服务端可以访问,客户端无法访问;

    • 通过.env文件配置的环境变量无法自定义环境关键字;

    • 通过.env文件配置的环境变量不会写入代码中,可考虑存储敏感信息(待验证)

    读取环境变量写入代码有三种方案:

    1. env配置,具体如上

      const nextConfig = withImages(
          ...
          env: {
            RUN_ENV: process.env.RUN_ENV
          },
        })
      );
      
      
    2. webpack配置

      const nextConfig = withImages(
        ...
        webpack: (config, options) => {
          config.plugins.push(new options.webpack.DefinePlugin({
             'process.browser': JSON.stringify(typeof window !== 'undefined')
             'process.env': JSON.stringify(process.env)
          }));
      
          return config;
        }
      );
      
    3. babelrc配置

      • 安装插件:

        npm install babel-plugin-transform-define --save-dev
        
      • .babelrc 或 Babel 配置中添加插件配置:

        {
          "presets": ["next/babel"],
          "plugins": [
            ["transform-define", { "process.browser": "typeof window !== 'undefined'" }]
          ]
        }
        

    参考文档:

    https://nextjs.org/docs/messages/non-standard-node-env

    https://www.51cto.com/article/773164.html

    路由守护

    中间件允许你在请求完成前运行代码。然后,您可以根据接收到的请求,通过重写、重定向、修改请求或响应标头或直接响应等方式修改响应。

    中间件在缓存内容和路径匹配之前运行。

    import { NextResponse } from 'next/server'
    import type { NextRequest } from 'next/server'
    
    export function middleware(request: NextRequest) {
      return NextResponse.redirect(new URL('/home', request.url))
    }
    
    export const config = {
      matcher: '/about/:path*',
    }
    

    多级状态管理Reducer

    useReducer:

    1. useReducer可以管理复杂的、具有多个相关状态的组件状态;

      • 如不想通过useState定义多排状态,可以考虑useReducer替代;

      • 可以进行新旧状态数据对比,类同useState((prev) => {})

    2. useReducer & useContext实现多级状态同步;

    1. useReducer & useContext多级状态管理封装
    import {
      createContext,
      Dispatch,
      useMemo,
      PropsWithChildren,
      useContext,
      useReducer
    } from 'react';
    import { useSearchParams } from 'react-router-dom';
    
    interface ContextStateDef {
      login: {
        loginToken: string;
        openId: string;
      }
    }
    enum ActionsEnum {
      setState = 1,
    }
    interface ContextActionDef {
      type: ActionsEnum.setState;
      payload: ContextStateDef['login'];
    }
    
    const initialState = { // 定义初始上下文
      login: {
        loginToken: '',
        openId: ''
      }
    };
    type ActionDef = ContextActionDef;
    const RootContextContainer = createContext<{  // 定义上下文,对应Provider的value必须符合当前类型;
      state: ContextStateDef;
      dispatch: Dispatch<ActionDef>;
    }>({
      state: initialState,
      dispatch: (() => {}) as Dispatch<ActionDef>
    });
    
    const reducer = (state: ContextStateDef, action: ActionDef) => {
      const { type, payload } = action;
      switch (type) {
        case ActionsEnum.setState:
          return {
            ...state,
            login: payload
          };
        default:
          return {
            ...state
          } as never;
      }
    };
    const useRootContext = () => {  // 封装Consumer hooks方法,避免原始上下文暴露,提供调试信息
      const rootContext = useContext(RootContextContainer);
      if (!rootContext) {
        throw new Error('useRootContext must be used within a RootProvider');
      }
      const updateContext = useMemo(() => {  // 避免context引发的无限渲染循环
        return rootContext.dispatch;
      }, [rootContext]);
      const context = useMemo(() => {  // 避免context引发的无限渲染循环
        return rootContext.state;
      }, [rootContext]);
      return {
        context,
        updateContext
      };
    };
    const RootProvider = ({ children }: PropsWithChildren) => {
      const [state, dispatch] = useReducer(reducer, initialState);
      const [searchParams] = useSearchParams();
      // Provider实现多级传递上下文
      return (
        <RootContextContainer.Provider value={{ state, dispatch }}>
          {children}
        </RootContextContainer.Provider>
      );
    };
    export type { ContextStateDef };
    export { ActionsEnum, useRootContext };
    export default RootProvider;
    
    1. Provider调用
    <RootProvider>
      <HeaderNavigation
        above
        defaultTheme={ThemeEnum.light}
        themeChangable
        transparent
      />
      {children}
      <FooterSiteMap />
    </RootProvider>
    
    1. Consumer调用
      // 使用时,通过useCallback、useMemo承接,创建缓存,避免引发无限渲染循环;
      const updateScroller = useCallback(() => {
        updateContext({
          type: ActionsEnum.setScroller,
          payload: scrollContainerRef
        });
      }, [updateContext, scrollContainerRef]);
      useEffect(() => {
        updateScroller();
      }, [updateScroller]);
    

    无限渲染循环

    有时可能会遇到无限循环的问题。这通常是因为在使用时,依赖项没有正确设置,导致组件在每次渲染时都会重新创建新的上下文。

    使用上下文时,需要通过useCallback、useMemo承接,组件使用memo()包裹,创建缓存,避免引发无限渲染循环

    降级CSR

    将Next应用打包为CSR,在服务崩溃时,通过Nginx负载均衡()指向客户端渲染。

    1. CSR导出配置
    //next.config.js
    const nextConfig = {  
      // https://nextjs.org/docs/app/building-your-application/deploying/static-exports
      output: 'export', 
    } 
    module.exports = nextConfig
    
    • next14导出静态资源时,只需要修改next.config.js即可,无需像v13版本使用next export

    • 执行next build,生成包含HTML/CSS/JS的out目录。

    • 通过browser-sync start --cors -s "./out"即可访问导出的静态页面。

    CSR针对部分next特性不支持,详见https://nextjs.org/docs/app/building-your-application/deploying/static-exports#unsupported-features

    1. 部署

      • 部署SSR

      • 部署CSR

    2. 宕机自动降级CSR

    http {
        include       mime.types;
        default_type  application/octet-stream;
    
        sendfile        on;
        keepalive_timeout  65;
        upstream backend {
            server host.ssr.com max_fails=3 fail_timeout=30s; // 在30s内重试3次都失败,则标记当前服务不可用,会走下边的bak.com服务。
            server bak.csr.com backup;  // 当上游服务器都不可用时,该服务接收Nginx请求。
        }  // 如果所有配置的上游服务器都无法响应,Nginx将返回错误给客户端。默认情况下,这通常是 502 Bad Gateway 错误。
        server {
            listen  80;                                                         
            server_name domain.com;
            index  index.php index.html index.htm;
    
            location / {
                proxy_pass http://backend;  //将请求domain.com的请求代理到upstream服务集群中,按负载均衡策略访问不同的服务器。 
                proxy_set_header Host $host;
            }
        }
    }
    

    FAQs

    1. 静态资源加载失败

    查看next.config#assetPrefix字段配置,该配置会影响js/css静态资源加载路径。

    1. 跳转失败,404

    程序内的页面跳转需要使用next内置的组件next/link,使用a无法正常跳转。

    参考文档

    https://juejin.cn/post/7338280070304809010

    https://blog.csdn.net/javaboyweng/article/details/97612605

    浏览器兼容

    ES6+垫片

    1. 安装相关依赖

      npm i -S core-js regenerator-runtime
      npm i -D @babel/core @babel/preset-env
      
    2. 根文件引入垫片

      // src/app/layout.tsx
      import 'core-js'
      import 'regenerator-runtime/runtime' // 支持async、await
      
    3. 配置.babelrc

      {
        "presets": [
          [
            "next/babel", 
            {
              "preset-env": { // 配置同@babel/preset-env
                "debug": true, // 开启调试模式,在命令行输出当前详细配置&执行。
                /**
                  * 设置为 "entry" 时,Babel 会根据 core-js 版本和 targets 配置,自动引入目标环境所需的所有 polyfills。这需要在入口文件中手动引入 core-js/stable(包含 ECMAScript 标准特性)和 regenerator-runtime/runtime(如果你使用了异步函数)。
                  * 使用 useBuiltIns: "usage" 时,不需要在代码中手动引入 core-js 或 regenerator-runtime,Babel 会根据需要自动处理这些引入。
                */
                "useBuiltIns": "entry",
                "corejs": {
                  "version": "3.36", // Warning! Recommended to specify used minor core-js version, like corejs: '3.36', instead of corejs: 3, since with corejs: 3 will not be injected modules which were added in minor core-js releases.
                  // "proposals": true
                },
                "targets": { 
                /**
                 * 如果在 Babel 的配置文件(如 .babelrc、babel.config.js)中直接指定了 targets,这将具有最高优先级。Babel 会忽略 .browserslistrc 文件或 package.json 中的 browserslist 配置,仅使用 targets 选项的设置。
                 * 两种设置模式:
                   * browserslist兼容模式
                   * browserslist模式
                */
                // "chrome": "49" // browserslist兼容模式
                  "browsers": [ // browserslist模式
                    ">0.02%", // 包含chrome 48+
                    "not op_mini all",
                    "not op_mob >= 1"
                  ]
                }
              },
              "transform-runtime": {},
              "styled-jsx": {},
              "class-properties": {}
            }
          ]
        ],
        "plugins": []
      }
      
    4. 配置next.config.mjs

    const nextConfig = withImages(
      ...
      webpack: (config, options) => {
        config.plugins.push(new options.webpack.DefinePlugin({
           'process.env': JSON.stringify(process.env)
        }));
    
        return config;
      }
    );
    
    1. 配置browserslist【必须】

      可通过npx browserslist查看当前支持的浏览器

      // package.json 
      {
        ...
        /**
         * 配置了.babelrc#targets,这个也需要配置,否则会报错。
         * 配置了browserslist,可以不用配置.babelrc#targets
        */
        "browserslist": [
          ">0.02%", // 包含chrome 48+
          "not op_mini all",
          "not op_mob >= 1"
        ]
      }
      

    FAQs

    • globalThis is not defined.

    报错代码回源:

    node_modules/next/dist/client/components/async-local-storage.js

    30L

    const maybeGlobalAsyncLocalStorage = globalThis.AsyncLocalStorage;

    解决方案:

    https://www.npmjs.com/package/globalthis

    修改代码库,追加下述代码:
    var globalThis = require('globalthis')()

    • Illegal Constructor error.

    报错代码回源:

    node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js

    new ReadableStream

    解决方案:

    https://github.com/MattiasBuelens/web-streams-polyfill/tree/masterIllegal

    在html顶部追加:

    <script src="``https://unpkg.com/web-streams-polyfill/dist/polyfill.js``"></script>

    参考文档

    https://www.jnielson.com/demystifying-babel-preset-env

    https://github.com/vercel/next.js/issues/44250

    https://cloud.tencent.com/developer/ask/sof/106622781

    相关文章

      网友评论

          本文标题:Nextjs之app router应用

          本文链接:https://www.haomeiwen.com/subject/ivgpcjtx.html