├── .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 |
--------------------------------------------------------------------------------