Overview
- SSR (Server Side Rendering) as a view template engine
- Passing the server data to the client
props
- Dynamic
props
without caring about SSR- Suitable for dynamic routes like blogging
- Dynamic
Head
component - HMR when
process.env.NODE_ENV !== 'production'
Usage
Install it:
# install NestJS dependencies
$ npm install --save @nestjs/core @nestjs/common @nestjs/platform-express
# install @xxx-ssr/nestjs-express
$ npm install --save @xxx-ssr/nestjs-express react react-dom
And add a script to your package.json like this:
{
"scripts": {
"start": "ts-node --project tsconfig.server.json server/main.ts"
}
}
Populate files below inside your project:
./.babelrc
{
"presets": ["@xxx-ssr/nestjs-express/babel"]
}
./tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "esnext"],
"strict": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"isolatedModules": true,
"resolveJsonModule": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
"exclude": ["node_modules", ".ssr"]
}
./tsconfig.server.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
},
"include": ["server"]
}
./server/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import register from '@xxx-ssr/nestjs-express/register';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// register `.tsx` as a view template engine
await register(app);
app.listen(3000, async () => {
console.log(`> Ready on http://localhost:3000`);
});
}
bootstrap();
./server/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
controllers: [AppController]
})
export class AppModule {}
./server/app.controller.ts
import { Controller, Get, Render } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
@Render('index') // this will render `views/index.tsx`
public showHome() {
const user = { name: 'NestJS' };
return { user };
}
}
./views/index.tsx
interface IndexProps {
user: any;
}
const Index = ({ user }: IndexProps) => {
return <p>Hello {user.name}!</p>;
};
export default Index;
Then just run npm start
and go to http://localhost:3000
, you'll see Hello NestJS!
.
ssr.config.js
)
Configuration (Here is the default ssr.config.js
, which is used by xxx-ssr
when there are no valid values:
module.exports = {
id: 'default',
distDir: '.ssr',
viewsDir: 'views',
dynamicViews: [],
webpack: (
config /* webpack.Configuration */,
env /* 'development' | 'production' */
) => {
return config;
}
};
ssr.config.js#id
The id of UI framework. (default: default
)
It can be ignored only when the project does not use any UI frameworks.
Supported UI frameworks are:
- [x] default (the id
default
doesn't need to be specified inssr.config.js
)- [x] semantic-ui
- [x] Or any other non CSS-in-JS UI frameworks
- [x] emotion
- [x] styled-components
- [x] material-ui
- [x] antd
- [ ] and more...
For example, if we want to use emotion
, ssr.config.js
is like this:
module.exports = {
id: 'emotion'
};
ssr.config.js#distDir
The place where xxx-ssr
generates production results. (default: .ssr
)
If we use TypeScript or any other library which must be compiled, the config below may be useful:
module.exports = {
// dist folder should be ignored by `.gitignore`
distDir: 'dist/.ssr'
};
ssr.config.js#viewsDir
The place where we put views. (default: views
)
A function res.render('xxx')
will render views/xxx.jsx
or views/xxx.tsx
.
A working example is here:
ssr.config.js#dynamicViews
If specified, xxx-ssr
won't generate html cache when production.
This is suitable for search-engine-optimized dynamic routes like blogging:
module.exports = {
dynamicViews: [
// this means `views/posts.jsx` is a dynamic view
'posts'
]
};
A working example is here:
ssr.config.js#webpack()
module.exports = {
webpack: (
config /* webpack.Configuration */,
env /* 'development' | 'production' */
) => {
// we can override default webpack config here
return config;
}
};
Custom Document
Just put _document.tsx
into the views root:
./views/_document.tsx
import React from 'react';
import { Document, Head, Main } from '@xxx-ssr/nestjs-express';
export default class extends Document {
render() {
return (
<html>
<Head>
<title>Default Title</title>
</Head>
<body>
<Main />
</body>
</html>
);
}
}
Note: Please put <Main />
component directly under <body>
tag AND don't wrap <Main />
component with another components because this is a hydration target for the client.
And then, use it as always:
./views/index.tsx
const Index = props => {
return <p>Hello Layout!</p>;
};
export default Index;
Head
Dynamic We can use the Head
component anyware:
./views/index.tsx
import React from 'react';
import { Head } from '@xxx-ssr/nestjs-express';
const Index = props => {
return (
<React.Fragment>
<Head>
<title>Dynamic Title</title>
<meta name="description" content="Dynamic Description" />
</Head>
<p>Of course, SSR Ready!</p>
</React.Fragment>
);
};
export default Index;
Supported UI Framework
- [x] default (the id
default
doesn't need to be specified inssr.config.js
)- [x] semantic-ui
- [x] Or any other non CSS-in-JS UI frameworks
- [x] emotion
- [x] styled-components
- [x] material-ui
- [x] antd
- [ ] and more...
Non CSS-in-JS framework
Like semantic-ui, non CSS-in-JS frameworks are supported without extra configuration.
All we have to do is to load global CSS in _document
or each page:
./views/_document.tsx
import React from 'react';
import { Document, Head, Main } from '@xxx-ssr/express';
export default class extends Document {
render() {
return (
<html>
<Head>
<title>A Sample of Semantic UI React</title>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css"
/>
</Head>
<body>
<Main />
</body>
</html>
);
}
}
With Emotion
In order to enable SSR, we must install these packages:
- @emotion/cache as dependencies
- create-emotion-server as dependencies
- babel-plugin-emotion as devDependencies
And then, populate .babelrc
in your project root:
{
"presets": ["@xxx-ssr/express/babel"],
"plugins": ["emotion"]
}
With styled-components
In order to enable SSR, we must install babel-plugin-styled-components
as devDependencies.
And then, populate .babelrc
in your project root:
{
"presets": ["@xxx-ssr/nestjs-express/babel"],
"plugins": ["styled-components"]
}
With Material UI
We can use material-ui without extra configuration.
With Ant Design
In order to enable SSR, we must install babel-plugin-import
as devDependencies.
And then, populate .babelrc
in your project root:
{
"presets": ["@xxx-ssr/nestjs-express/babel"],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": "css"
}
]
]
}