├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── index.ts └── lib │ ├── nest-next.module.ts │ ├── next-server.provider.ts │ ├── next.controller.ts │ ├── next.middleware.ts │ └── types.ts ├── tsconfig.check.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 2 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "project": "./tsconfig.json" 6 | }, 7 | "extends": [ 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:prettier/recommended", 10 | "prettier", 11 | "prettier/@typescript-eslint" 12 | ], 13 | "rules": { 14 | "@typescript-eslint/explicit-function-return-type": "off", 15 | "@typescript-eslint/no-empty-interface": "off" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .vscode 64 | dist 65 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | !dist 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.3.1 2 | 3 | - 支持next9.1.1 4 | 5 | ## 0.2.8 6 | 7 | - 修复module的类型错误 8 | 9 | ## 0.2.7 10 | 11 | - 修改cahce模块作为依赖引入, 需要提供对应的拦截器服务 12 | 13 | ## 0.2.6 14 | 15 | - 增加了cache模块 16 | 17 | ## 0.2.4 18 | 19 | - update deps 20 | 21 | ## 0.2.3 22 | 23 | - fix bugs 24 | 25 | ## 0.2.0 26 | 27 | - update nextjs to 9.0.0 28 | 29 | ## 0.1.8 30 | 31 | - update dependencies 32 | - update prettier config 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ananiy Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nest-next-module 2 | 3 | ## 简介 4 | 5 | 在[nestjs](https://nestjs.com)中使用[nextjs](https://nextjs.org)做模板渲染引擎. 6 | 7 | 由于需求要做SEO, 自然而然的想到了SSR. 在对比`Angular Universal`, `nuxtjs`, `nextjs`之后, 选择了`nextjs`. 8 | 9 | 同时发现, `nextjs`如果要增加动态路由等功能, 需要后台去做支持, 于是选择了`typescript`支持最完善的`nestjs`框架. 10 | 11 | 于是如何将`nestjs`与`nextjs`一起使用, 就成了我们主要解决的问题. 12 | 13 | 在`nestjs`和`nextjs`中都包含一个`node http`的实现, 因此需要将`nestjs`的页面路由请求, 转发给`nextjs`去处理渲染模板, 以及一些静态资源的访问也需要做转发. 14 | 15 | 在`nestjs`的[issues 1122](https://github.com/nestjs/nest/issues/1122)中[Kyle McCarthy](https://github.com/kyle-mccarthy)写了一个[nest-next](https://github.com/kyle-mccarthy/nest-next)包, 仔细阅读了其源码之后, 我决定自己写一个绑定模块. 16 | 17 | 原因主要是因为`nest-next`篡改了`nestjs`中`express`中的模板引擎, 侵入性比较强, 如果我还需要用`express`的模板渲染的话, 是不可能做到的. 其次`nest-next`的模块导入方式不符合`nestjs`模块正常使用方式, 需要手动启动`next-server`并绑定. 18 | 19 | ## 注意! 20 | 21 | 目前nextjs最新版为9.x版本,如果要在9.x版本中使用本模块,请参考: 22 | 23 | https://github.com/ananiy/nest-next-module/tree/release/v0.1.8 24 | 25 | 并使用[v0.1.8](https://www.npmjs.com/package/nest-next-module/v/0.1.8)版本 26 | 27 | ## Demo 28 | 29 | [nest-next-module-demo](https://github.com/ananiy/nest-next-module-demo/tree/release/v0.2.3) 30 | 31 | ## 开始使用 32 | 33 | - 安装`@nestjs/cli`, 新建一个项目: 34 | 35 | ```bash 36 | $ npm i -g @nestjs/cli 37 | $ nest new nest-next-demo # 请选择使用yarn安装模块 38 | ``` 39 | 40 | - 安装`NestNextModule`包, 和`nextjs`相关依赖: 41 | 42 | ```bash 43 | $ yarn add nest-next-module next react react-dom 44 | ``` 45 | 46 | - 在`AppModule`中导入`NestNextModule`: 47 | 48 | ```ts 49 | // src/app.module.ts 50 | 51 | import { Module } from '@nestjs/common'; 52 | import { AppController } from './app.controller'; 53 | import { AppService } from './app.service'; 54 | import { NestNextModule } from 'nest-next-module'; 55 | 56 | const dev = process.env.NODE_ENV !== 'production'; 57 | 58 | @Module({ 59 | imports: [NestNextModule.forRoot({ dev })], 60 | controllers: [AppController], 61 | providers: [AppService], 62 | }) 63 | export class AppModule {} 64 | ``` 65 | 66 | - 在`AppController`中创建路由, 使用`nextjs`的`render`渲染模板: 67 | 68 | ```ts 69 | // src/app.controller.ts 70 | 71 | import { Controller, Get, Res } from '@nestjs/common'; 72 | import { NextResponse } from 'nest-next-module'; 73 | 74 | @Controller() 75 | export class AppController { 76 | @Get() 77 | index(@Res() res: NextResponse) { 78 | return res.nextRender('/index'); 79 | } 80 | } 81 | ``` 82 | 83 | - 根目录新建`pages`文件夹, 新建`index.jsx`文件: 84 | 85 | ```jsx 86 | // pages/index.tsx 87 | 88 | import React from 'react'; 89 | 90 | const Page = () => { 91 | return

hello nest next!

; 92 | }; 93 | 94 | export default Page; 95 | ``` 96 | 97 | - 启动项目: 98 | 99 | ```bash 100 | $ yarn start 101 | ``` 102 | 103 | - 打开浏览器, 访问`http://localhost:3000/`. 104 | 105 | ## 在nextjs中使用typescript 106 | 107 | 最新版的nextjs已经内置了ts的支持, 因此可以直接使用ts/tsx 108 | 109 | 但是到目前为止,nextjs 9.0.x版本会默认覆盖tsconfig.json,并且设置module为esnext,导致nestjs无法编译 110 | 111 | 所以需要单独新建nestjs的tsconfig文件,改module为commonjs,具体可以参考demo中的配置 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-next-module", 3 | "version": "0.5.4", 4 | "description": "using nestjs and nextjs together!", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "rimraf dist && tsc" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ananiy/nest-next-module.git" 12 | }, 13 | "keywords": [ 14 | "nestjs", 15 | "nextjs", 16 | "nestjs-module" 17 | ], 18 | "author": "ananiy", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/ananiy/nest-next-module/issues" 22 | }, 23 | "homepage": "https://github.com/ananiy/nest-next-module#readme", 24 | "dependencies": { 25 | "@nestjs/common": "^6.10.8", 26 | "@nestjs/core": "^6.10.8", 27 | "@types/express": "^4.17.2", 28 | "express": "^4.17.1", 29 | "next": "^9.1.5" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "^12.12.16", 33 | "@typescript-eslint/eslint-plugin": "^2.11.0", 34 | "@typescript-eslint/parser": "^2.11.0", 35 | "eslint": "^6.7.2", 36 | "eslint-config-prettier": "^6.7.0", 37 | "eslint-plugin-prettier": "^3.1.1", 38 | "husky": "^3.1.0", 39 | "lint-staged": "^9.5.0", 40 | "prettier": "^1.19.1", 41 | "rimraf": "^3.0.0", 42 | "typescript": "^3.7.3" 43 | }, 44 | "husky": { 45 | "hooks": { 46 | "pre-commit": "lint-staged && tsc -p tsconfig.check.json" 47 | } 48 | }, 49 | "lint-staged": { 50 | "*.{ts,tsx,js,jsx}": [ 51 | "prettier --check", 52 | "eslint", 53 | "git add" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | NextRequest, 3 | NextResponse, 4 | NextServer, 5 | NextServerOptions, 6 | } from './lib/types' 7 | export { NestNextModule } from './lib/nest-next.module' 8 | -------------------------------------------------------------------------------- /src/lib/nest-next.module.ts: -------------------------------------------------------------------------------- 1 | import { NestModule, MiddlewareConsumer, Module } from '@nestjs/common' 2 | import { DynamicModule } from '@nestjs/common/interfaces' 3 | import { createNextServer } from './next-server.provider' 4 | import { NextMiddleware } from './next.middleware' 5 | import { NextController } from './next.controller' 6 | import { NextServerOptions } from './types' 7 | 8 | @Module({}) 9 | export class NestNextModule implements NestModule { 10 | static forRoot(nextServerOptions: NextServerOptions): DynamicModule { 11 | const nextServer = createNextServer(nextServerOptions) 12 | 13 | return { 14 | module: NestNextModule, 15 | controllers: [NextController], 16 | providers: [nextServer], 17 | exports: [nextServer], 18 | } 19 | } 20 | configure(consumer: MiddlewareConsumer): void { 21 | consumer.apply(NextMiddleware).forRoutes('*') 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/next-server.provider.ts: -------------------------------------------------------------------------------- 1 | import Next from 'next' 2 | 3 | import { FactoryProvider } from '@nestjs/common/interfaces' 4 | import { NextServer, NextServerOptions } from './types' 5 | 6 | export const NextServerToken = 'NextServerToken' 7 | 8 | export const createNextServer = ( 9 | nextServerOptions: NextServerOptions 10 | ): FactoryProvider> => ({ 11 | provide: NextServerToken, 12 | useFactory: async () => { 13 | const nextServer = Next(nextServerOptions) 14 | await nextServer.prepare() 15 | return nextServer 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /src/lib/next.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, Res } from '@nestjs/common' 2 | import { NextResponse, NextRequest } from './types' 3 | 4 | @Controller() 5 | export class NextController { 6 | @Get('*') 7 | allHandler(@Req() req: NextRequest, @Res() res: NextResponse) { 8 | return res.nextRequestHandler(req, res) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/next.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware, Inject } from '@nestjs/common' 2 | import { NextServerToken } from './next-server.provider' 3 | import { NextServer, NextRequest, NextResponse } from './types' 4 | 5 | @Injectable() 6 | export class NextMiddleware 7 | implements NestMiddleware { 8 | constructor(@Inject(NextServerToken) private nextServer: NextServer) {} 9 | 10 | use(req: NextRequest, res: NextResponse, next: () => void): void { 11 | res.nextServer = this.nextServer 12 | res.nextRender = this.nextServer.render.bind(this.nextServer, req, res) 13 | res.nextRequestHandler = this.nextServer.getRequestHandler() 14 | next() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import Next, { NextApiRequest, NextApiResponse } from 'next' 3 | import { ParsedUrlQuery } from 'querystring' 4 | import { UrlWithParsedQuery } from 'url' 5 | 6 | export type NextServer = ReturnType 7 | export type NextServerOptions = Parameters[0] 8 | 9 | export interface NextRequest 10 | extends Request, 11 | Omit {} 12 | 13 | export interface NextResponse 14 | extends Response, 15 | Omit { 16 | nextServer: NextServer 17 | nextRender: ( 18 | pathname: string, 19 | query?: ParsedUrlQuery, 20 | parsedUrl?: UrlWithParsedQuery 21 | ) => Promise 22 | nextRequestHandler: ReturnType 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | "jsx": "preserve" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 10 | "declaration": true /* Generates corresponding '.d.ts' file. */, 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true /* Generates corresponding '.map' file. */, 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist" /* Redirect output structure to the directory. */, 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "incremental": true /* Enable incremental compilation */, 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | "skipLibCheck": true, 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 61 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ 62 | } 63 | } 64 | --------------------------------------------------------------------------------