├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── pr-checks.yml ├── .gitignore ├── .prettierrc ├── README.md ├── babel.config.js ├── jest.config.js ├── jest.setup.js ├── package.json ├── src ├── index.test.js └── index.tsx ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | .next/* 2 | coverage/* 3 | locales/* 4 | deploy/* 5 | mock/* 6 | cypress/* -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb", 4 | "prettier", 5 | "prettier/react", 6 | "prettier/@typescript-eslint", 7 | "plugin:@typescript-eslint/recommended" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 2018, // Allows for the parsing of modern ECMAScript features 11 | "sourceType": "module" // Allows for the use of imports 12 | }, 13 | "parser": "@typescript-eslint/parser", 14 | "rules": { 15 | "import/extensions": [ 16 | "error", 17 | "ignorePackages", 18 | { 19 | "js": "never", 20 | "jsx": "never", 21 | "ts": "never", 22 | "tsx": "never" 23 | } 24 | ], 25 | "react/jsx-props-no-spreading": "off", 26 | "no-console": "off", 27 | "react/prop-types": "off", 28 | "react/default-props-match-prop-types": "off", 29 | "react/jsx-no-duplicate-props": ["error", { "ignoreCase": false }], 30 | "react/no-did-mount-set-state": "off", 31 | "react/jsx-filename-extension": [ 32 | 1, 33 | { 34 | "extensions": [".js", ".tsx"] 35 | } 36 | ], 37 | "@typescript-eslint/no-var-requires": "warn" 38 | }, 39 | "plugins": [ 40 | "react", 41 | "prettier", 42 | "import", 43 | "@typescript-eslint", 44 | "react-hooks" 45 | ], 46 | "env": { 47 | "browser": true, 48 | "jest": true 49 | }, 50 | "overrides": [ 51 | { 52 | "files": ["*.test.js"], 53 | "rules": { 54 | "global-require": 0, 55 | "no-underscore-dangle": 0 56 | } 57 | } 58 | ], 59 | "settings": { 60 | "react": { 61 | "version": "detect" 62 | }, 63 | "import/resolver": { 64 | "node": { 65 | "extensions": [".js", ".jsx", ".ts", ".tsx", ".json"] 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.github/workflows/pr-checks.yml: -------------------------------------------------------------------------------- 1 | name: PR checks 2 | 3 | env: 4 | GPR_TOKEN: ${{ secrets.GPR_TOKEN }} 5 | HUSKY_SKIP_INSTALL: true 6 | 7 | on: pull_request 8 | 9 | jobs: 10 | eslint: 11 | runs-on: ubuntu-18.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 12 17 | - name: Restore node_modules cache 18 | uses: actions/cache@v2 19 | with: 20 | path: '**/node_modules' 21 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 22 | - name: Install packages 23 | run: yarn --frozen-lockfile 24 | - name: 'ESLint' 25 | run: yarn eslint 26 | typecheck: 27 | runs-on: ubuntu-18.04 28 | steps: 29 | - uses: actions/checkout@v2 30 | - uses: actions/setup-node@v1 31 | with: 32 | node-version: 12 33 | - name: Restore node_modules cache 34 | uses: actions/cache@v2 35 | with: 36 | path: '**/node_modules' 37 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 38 | - name: Install packages 39 | run: yarn --frozen-lockfile 40 | - name: 'Type check' 41 | run: yarn typecheck 42 | tests: 43 | runs-on: ubuntu-18.04 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions/setup-node@v1 47 | with: 48 | node-version: 12 49 | - name: Restore node_modules cache 50 | uses: actions/cache@v2 51 | with: 52 | path: '**/node_modules' 53 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 54 | - name: Install packages 55 | run: yarn --frozen-lockfile 56 | - name: 'Tests' 57 | run: yarn test 58 | build: 59 | runs-on: ubuntu-18.04 60 | steps: 61 | - uses: actions/checkout@v2 62 | - uses: actions/setup-node@v1 63 | with: 64 | node-version: 12 65 | - name: Restore node_modules cache 66 | uses: actions/cache@v2 67 | with: 68 | path: '**/node_modules' 69 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 70 | - name: Install packages 71 | run: yarn --frozen-lockfile 72 | - name: 'Build' 73 | run: yarn prepublishOnly -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | dist/ 4 | coverage/ 5 | .npmrc -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": true, 6 | "jsxSingleQuote":false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # next-app-builder 2 | 3 | [![Version Badge][npm-version-svg]][package-url] 4 | [![GZipped size][npm-minzip-svg]][bundlephobia-url] 5 | [![dependency status][deps-svg]][deps-url] 6 | [![dev dependency status][dev-deps-svg]][dev-deps-url] 7 | [![License][license-image]][license-url] 8 | [![Downloads][downloads-image]][downloads-url] 9 | 10 | Custom App builder for Next.js. 11 | 12 | ## What is a Custom `App`? 13 | 14 | Next.js uses the App component to initialize pages. You can override it and control the page initialization. Which allows you to do amazing things like: 15 | 16 | - Persisting layout between page changes 17 | - Keeping state when navigating pages 18 | - Custom error handling using componentDidCatch 19 | - Inject additional data into pages 20 | - Add global CSS 21 | 22 | For more details, see [offical documentation](https://nextjs.org/docs/advanced-features/custom-app). 23 | 24 | ## Why a builder? 25 | 26 | Generates a custom next App using middleware. 27 | 28 | Before: 29 | 30 | ```javascript 31 | class CustomNextApp extends App { 32 | static async getInitialProps({ Component, ctx, router }) { 33 | const initialPageProps = await (Component.getInitialProps ? Component.getInitialProps : {}); 34 | const data = await fetch(getDataForPage(router.pathname)); 35 | return { 36 | pageProps: { 37 | ...initialPageProps, 38 | data 39 | } 40 | }; 41 | } 42 | 43 | render() { 44 | const { Component, pageProps } = this.props; 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | ``` 55 | 56 | After: 57 | 58 | ```javascript 59 | const ssrDataMiddleware = { 60 | Component: SsrDataProvider, 61 | getInitialProps: ({ router }) => { 62 | const data = await fetch(getDataForPage(router.pathname)); 63 | return { data }; 64 | } 65 | }; 66 | 67 | const layoutMiddleware = { Component: LayoutComponent }; 68 | 69 | nextAppBuilder({ 70 | middleware: [ssrDataMiddleware, layoutMiddleware] 71 | }); 72 | ``` 73 | 74 | ## Installation 75 | 76 | Install using Yarn: 77 | 78 | ```sh 79 | yarn add @scacap/next-app-builder 80 | ``` 81 | 82 | or NPM: 83 | 84 | ```sh 85 | npm install @scacap/next-app-builder --save 86 | ``` 87 | 88 | ## Usage 89 | 90 | ```javascript 91 | // pages/_app.js 92 | import nextAppBuilder from '@scacap/next-app-builder'; 93 | import materialUiMiddleware from '../middlewares/material-ui'; 94 | import theme from '../theme'; 95 | 96 | export default nextAppBuilder({ 97 | middleware: [materialUiMiddleware(theme)] 98 | }); 99 | ``` 100 | 101 | [![Edit useInView](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/custom-next-app-15s4p?fontsize=14&hidenavigation=1&file=/pages/_app.tsx) 102 | 103 | ## API 104 | 105 | ### nextAppBuilder 106 | 107 | Creates a custom app that should be the default export of `pages/_app.js`. 108 | 109 | ```javascript 110 | const CustomApp = appBuilder({ 111 | middleware: [ 112 | // middlewares here 113 | ] 114 | }); 115 | ``` 116 | 117 | ### middleware 118 | 119 | An object containing the following fields: 120 | 121 | - **name**: a human readable identifier of middleware. Optional. 122 | - **getInitialProps**: a function which is executed before rendering. Used for blocking data requirements for every single page in your application, e.g. server side data fetching. Optional. 123 | - **Component**: the React component which is rendered in custom App. It's a wrapper component that will receive each page as children. Typically used for adding providers in the App level, e.g. css theme provider. Optional. 124 | - **componentDidCatch**: invoked when a descendant component throws an error. See more details in the [React docs](https://reactjs.org/docs/react-component.html#componentdidcatch). Optional. 125 | 126 | ## Caveats 127 | 128 | Internally, you will be adding a custom getInitialProps in your App. This will disable Automatic Static Optimization in pages without Static Generation. 129 | 130 | For more details, see [offical documentation](https://nextjs.org/docs/advanced-features/custom-app#caveats). 131 | 132 | ## Contributing 133 | 134 | Let's build together our v1! Pull-requests and issue reports are welcome. 135 | 136 | [npm-version-svg]: https://img.shields.io/npm/v/@scacap/next-app-builder.svg 137 | [package-url]: https://www.npmjs.com/package/@scacap/next-app-builder 138 | [bundlephobia-url]: https://bundlephobia.com/result?p=@scacap/next-app-builder 139 | [npm-minzip-svg]: https://img.shields.io/bundlephobia/minzip/@scacap/next-app-builder 140 | [deps-url]: https://david-dm.org/scacap/next-app-builder 141 | [deps-svg]: https://david-dm.org/scacap/next-app-builder.svg 142 | [dev-deps-url]: https://david-dm.org/scacap/next-app-builder?type=dev 143 | [dev-deps-svg]: https://david-dm.org/scacap/next-app-builder/dev-status.svg 144 | [license-url]: https://www.apache.org/licenses/LICENSE-2.0 145 | [license-image]: https://img.shields.io/npm/l/@scacap/next-app-builder.svg 146 | [downloads-url]: https://npm-stat.com/charts.html?package=@scacap/next-app-builder 147 | [downloads-image]: https://img.shields.io/npm/dm/@scacap/next-app-builder.svg 148 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | module.exports = api => { 3 | api.cache(true); 4 | return { 5 | presets: ['@babel/env', '@babel/preset-react', '@babel/typescript'], 6 | plugins: [ 7 | '@babel/proposal-object-rest-spread', 8 | '@babel/plugin-proposal-nullish-coalescing-operator', 9 | '@babel/plugin-proposal-class-properties', 10 | '@babel/plugin-proposal-optional-chaining', 11 | '@babel/plugin-transform-runtime' 12 | ] 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | setupFiles: ['./jest.setup.js'], 5 | testPathIgnorePatterns: ['/node_modules/'], 6 | collectCoverage: true, 7 | collectCoverageFrom: ['src/**/*.{ts,tsx,js}'], 8 | coverageReporters: ['lcov', 'text', 'json-summary', 'json'], 9 | testMatch: ['**/?(*.)+(test).{ts,tsx,js}'], 10 | testURL: 'http://localhost' 11 | } 12 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | const { configure } = require('enzyme'); 2 | const Adapter = require('enzyme-adapter-react-16'); 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scacap/next-app-builder", 3 | "version": "0.0.1", 4 | "description": "Middleware pipeline to create next.js App.", 5 | "main": "./dist/index.js", 6 | "author": "frontend@sclable.capital", 7 | "peerDependencies": { 8 | "next": "^11.1.1", 9 | "react": "^16.13.1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/ScaCap/next-app-builder.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/ScaCap/next-app-builder/issues" 17 | }, 18 | "license": "Apache-2.0", 19 | "scripts": { 20 | "eslint-fix": "eslint './src/**/*.{ts,tsx}' --fix", 21 | "eslint": "eslint './src/**/*.{ts,tsx}'", 22 | "cleanup": "rm -rf dist", 23 | "build:types": "tsc --emitDeclarationOnly", 24 | "build:transpile": "babel src --extensions '.js,.ts,.tsx' --out-dir dist", 25 | "prepublishOnly": "yarn cleanup && yarn build:transpile && yarn build:types", 26 | "postpublish": "yarn cleanup", 27 | "typecheck": "tsc --noEmit", 28 | "test": "jest" 29 | }, 30 | "private": false, 31 | "devDependencies": { 32 | "@babel/cli": "7.16.0", 33 | "@babel/core": "7.16.0", 34 | "@babel/plugin-proposal-class-properties": "7.16.0", 35 | "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.0", 36 | "@babel/plugin-proposal-object-rest-spread": "7.16.0", 37 | "@babel/plugin-proposal-optional-chaining": "7.16.0", 38 | "@babel/plugin-transform-runtime": "7.16.4", 39 | "@babel/preset-env": "7.16.4", 40 | "@babel/preset-react": "7.16.0", 41 | "@babel/preset-typescript": "7.16.0", 42 | "@types/react": "16.9.35", 43 | "@types/react-dom": "16.9.8", 44 | "@typescript-eslint/eslint-plugin": "2.33.0", 45 | "@typescript-eslint/parser": "2.33.0", 46 | "babel-eslint": "10.0.3", 47 | "babel-loader": "8.1.0", 48 | "enzyme": "3.11.0", 49 | "enzyme-adapter-react-16": "1.15.5", 50 | "eslint": "6.8.0", 51 | "eslint-config-airbnb": "18.0.1", 52 | "eslint-config-prettier": "6.9.0", 53 | "eslint-plugin-import": "2.20.0", 54 | "eslint-plugin-jsx-a11y": "6.2.3", 55 | "eslint-plugin-prettier": "3.1.2", 56 | "eslint-plugin-react": "7.18.0", 57 | "eslint-plugin-react-hooks": "2.3.0", 58 | "jest": "26.6.3", 59 | "next": "12.3.1", 60 | "prettier": "2.1.2", 61 | "react": "16.13.1", 62 | "react-dom": "16.12.0", 63 | "ts-loader": "7.0.4", 64 | "typescript": "3.9.2" 65 | }, 66 | "files": [ 67 | "dist" 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import appBuilder from '.'; 4 | 5 | describe('App Builder', () => { 6 | const Page = ({ name }) =>
{`Hello ${name}!`}
; 7 | Page.getInitialProps = () => ({ name: 'Maria' }); 8 | 9 | const shallowPage = async middleware => { 10 | const App = appBuilder({ middleware }); 11 | const props = await App.getInitialProps({ Component: Page, ctx: { AppTree: () => null } }); 12 | return shallow(); 13 | }; 14 | 15 | it('should render Page correctly', async () => { 16 | const wrapper = await shallowPage([]); 17 | const page = wrapper.find(Page); 18 | expect(page.exists()).toBe(true); 19 | expect(page.html()).toContain('Hello Maria!'); 20 | expect(page.props()).toEqual(expect.objectContaining({ name: 'Maria' })); 21 | }); 22 | 23 | it('should render middleware Components & page in correct order', async () => { 24 | const middleware1 = { Component: ({ children }) =>
{children}
}; 25 | middleware1.Component.displayName = 'Component1'; 26 | const middleware2 = { Component: ({ children }) =>
{children}
}; 27 | middleware2.Component.displayName = 'Component2'; 28 | 29 | const wrapper = await shallowPage([middleware1, middleware2]); 30 | 31 | const firstElement = wrapper.find(middleware1.Component); 32 | const secondElement = firstElement.find(middleware2.Component); 33 | expect(secondElement.find(Page).exists()).toBe(true); 34 | }); 35 | 36 | it('should pass getInitialProps only to same middleware Component', async () => { 37 | const middleware1 = { 38 | Component: ({ children }) =>
{children}
, 39 | getInitialProps: () => ({ value: 7 }) 40 | }; 41 | const middleware2 = { 42 | Component: ({ children }) =>
{children}
, 43 | getInitialProps: () => ({ value: 42, name: 'Max' }) 44 | }; 45 | const wrapper = await shallowPage([middleware1, middleware2]); 46 | expect(wrapper.find(Page).props()).toEqual(expect.objectContaining({ name: 'Maria' })); 47 | expect(wrapper.find(middleware1.Component).props()).toEqual( 48 | // partial comparison because it contains children 49 | expect.objectContaining({ value: 7, name: 'Maria' }) 50 | ); 51 | expect(wrapper.find(middleware2.Component).props()).toEqual( 52 | // partial comparison because it contains children 53 | expect.objectContaining({ value: 42, name: 'Max' }) 54 | ); 55 | }); 56 | 57 | it('should pass the props correctly to the Component, when the internalRenderPage is called', async () => { 58 | let render; 59 | const middleware = { 60 | Component: jest.fn(({ children }) =>
{children}
), 61 | getInitialProps: ({ AppTree }) => { 62 | // we are "rendering" the component 63 | render = shallow(); 64 | return { internal: false }; 65 | } 66 | }; 67 | 68 | const App = appBuilder({ middleware: [middleware] }); 69 | const AppTree = jest.fn(() => null); 70 | const props = await App.getInitialProps({ Component: Page, ctx: { AppTree } }); 71 | expect(render.find(AppTree).exists()).toBe(true); 72 | expect(render.find(AppTree).prop('pageProps').internal).toBe(true); 73 | 74 | mount(); 75 | 76 | expect(middleware.Component).toHaveBeenCalledTimes(1); 77 | 78 | expect(middleware.Component.mock.calls[0][0]).toEqual( 79 | expect.objectContaining({ internal: false }) 80 | ); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ErrorInfo, FunctionComponent } from 'react'; 2 | import App from 'next/app'; 3 | import { AppContext } from 'next/dist/pages/_app'; 4 | 5 | export type NextAppMiddleware> = { 6 | /** 7 | * human readable identifier of middleware 8 | */ 9 | name?: string; 10 | /** 11 | * Static function which is executed before rendering. 12 | * Used for blocking data requirements for every single page in your application, e.g. server side data fetching. 13 | */ 14 | getInitialProps?(appContext: AppContext): T | Promise; 15 | /** 16 | * Component which is rendered in custom App. 17 | */ 18 | Component?: FunctionComponent; 19 | componentDidCatch?(error: Error, errorInfo: ErrorInfo): App['componentDidCatch']; 20 | }; 21 | 22 | type NextAppBuilderOptions = { 23 | middleware: NextAppMiddleware[]; 24 | }; 25 | 26 | type NextAppMiddlewareBuilder = (options: NextAppBuilderOptions) => typeof App; 27 | 28 | type ExecuteComponentDidCatchMiddleware = (allMiddlewarec, error: Error, _errorInfo: ErrorInfo) => void; 29 | 30 | const executeComponentDidCatchMiddleware: ExecuteComponentDidCatchMiddleware = (allMiddleware, error, errorInfo) => 31 | allMiddleware.forEach(({ componentDidCatch }) => { 32 | if (componentDidCatch) { 33 | componentDidCatch(error, errorInfo); 34 | } 35 | }); 36 | 37 | const renderPage = (allMiddleware, { Component: PageComponent, pageProps: { middlewareProps, ...props } }) => 38 | allMiddleware 39 | .filter(({ Component: MiddlewareComponent }) => !!MiddlewareComponent) 40 | .reduceRight( 41 | (nestedElement, { Component: MiddlewareComponent, id }) => ( 42 | 43 | {nestedElement} 44 | 45 | ), 46 | 47 | ); 48 | 49 | /** 50 | * Generates a custom next App using middleware. 51 | * 52 | * Usage 53 | * 54 | * ``` 55 | * 56 | * const getInitialProps = ({ router }) => { 57 | * const data = await fetch(getDataForPage(router.pathname)); 58 | * return { data }; 59 | * } 60 | * 61 | * const ssrDataMiddleware = { 62 | * Component: SsrDataProvider, 63 | * getInitialProps 64 | * }; 65 | * const layoutMiddleware = { Component: LayoutComponent }; 66 | * 67 | * nextAppBuilder({ 68 | * middleware: [ 69 | * ssrDataMiddleware, 70 | * layoutMiddleware 71 | * ] 72 | * }) 73 | * 74 | * ``` 75 | * 76 | * @param middleware 77 | */ 78 | const nextAppBuilder: NextAppMiddlewareBuilder = ({ middleware = [] }) => { 79 | const allMiddleware = middleware.map((singleMiddleware, index) => ({ 80 | ...singleMiddleware, 81 | id: `nextAppMiddleware-${index}` 82 | })); 83 | 84 | class NextAppMiddlewareComponent extends App { 85 | static async getInitialProps({ Component, ctx, router }): Promise<{ pageProps: any }> { 86 | let pageProps = {}; 87 | const { AppTree } = ctx; 88 | const extendPageProps = props => { 89 | pageProps = { 90 | ...pageProps, 91 | ...props 92 | }; 93 | }; 94 | if (Component.getInitialProps) { 95 | extendPageProps(await Component.getInitialProps(ctx)); 96 | } 97 | 98 | let middlewareProps; 99 | 100 | const InternalAppTree = props => { 101 | const enhancedPageProps = { ...pageProps, middlewareProps, ...props }; 102 | return ; 103 | }; 104 | 105 | const allInitialProps = await Promise.all( 106 | allMiddleware.map(async ({ getInitialProps, id, name }) => { 107 | let initialProps = {}; 108 | if (getInitialProps) { 109 | try { 110 | initialProps = await getInitialProps({ 111 | Component, 112 | router, 113 | ctx, 114 | AppTree: InternalAppTree 115 | }); 116 | } catch (error) { 117 | console.warn(`getInitialProps failed for middleware with name ${name || 'unnamed'}`, error); 118 | } 119 | } 120 | return { initialProps, id }; 121 | }) 122 | ); 123 | 124 | middlewareProps = allInitialProps.reduce( 125 | (props, { id, initialProps }) => ({ 126 | ...props, 127 | [id]: initialProps 128 | }), 129 | {} 130 | ); 131 | 132 | extendPageProps({ middlewareProps }); 133 | return { pageProps }; 134 | } 135 | 136 | componentDidCatch(error, errorInfo): void { 137 | executeComponentDidCatchMiddleware(allMiddleware, error, errorInfo); 138 | // This is needed to render errors correctly in development / production 139 | // eslint-disable-next-line 140 | // @ts-ignore 141 | super.componentDidCatch(error, errorInfo); 142 | } 143 | 144 | render(): JSX.Element { 145 | const { Component, pageProps, ...otherProps } = this.props; 146 | 147 | return renderPage(allMiddleware, { 148 | Component, 149 | // eslint-disable-next-line 150 | // @ts-ignore 151 | pageProps: { ...pageProps, ...otherProps } 152 | }); 153 | } 154 | } 155 | return NextAppMiddlewareComponent; 156 | }; 157 | 158 | export default nextAppBuilder; 159 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "declaration": true, 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "strictNullChecks": true, 10 | "noImplicitAny": false, 11 | "noImplicitThis": true, 12 | "alwaysStrict": true, 13 | "strictBindCallApply": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "jsx": "react", 17 | "moduleResolution": "node", 18 | "noEmit": false, 19 | "forceConsistentCasingInFileNames": true, 20 | "noErrorTruncation": true, 21 | "resolveJsonModule": true, 22 | "lib": ["es6", "dom"], 23 | "esModuleInterop": true, 24 | "listEmittedFiles":true 25 | }, 26 | "exclude": ["node_modules", "**/*.test.js", "dist"], 27 | "include": ["src"] 28 | } 29 | --------------------------------------------------------------------------------