Help ssr, use concent to add some material to the nextjs application
Tags: react, concent, javascript
Published: 2020-12-21 10:32:18

Open source is not easy, thank you for your support, [❤ star concent^_^](https://github.com/concentjs/concent)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33479f07d8ab4b75b6724c7d7eabe9c8~tplv-k3u1fbpfcp-watermark.image)


## Preface
`nextjs` is a very popular React server-side rendering application framework. It is very lightweight, easy to use, and has an active community. So when we use `react` to write an application that requires `ssr` (server side render), it is basically The city’s first choice is `nextjs`. `concent` is a new generation of `react` state management solution. It has a built-in dependency collection system. It also has the characteristics of 0 intrusion, predictability, gradual and high performance, and provides `lifecyle`, `composition api` and other flexible APIs are super simple to write, allowing you to easily control ultra-large-scale react applications.

## Hello next
Here we will use the `create-next-app` command to install a basic next sample application
```sh
npx create-next-app hello-next
```
After execution, you can see a directory structure as follows
```
|____public
|____pages
| |____ _app.js // The default root component of the next application
| |____index.js // Default homepage
| |____api // api routing file
| | |____hello.js
```
After we execute `npm run dev` in the project root directory, we will see a default homepage of `ssr` driven by `next`
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f739617e7fff4cb1a0bc955b51a48ffd~tplv-k3u1fbpfcp-watermark.image)

## Hello concent
Here we will use the `create-react-app` command to install a basic concent sample application
```sh
npx create-react-app hello-concent --template concent-ts
```
After execution, you can see a directory structure as follows
```
|____index.tsx
|____App.tsx
|____types // store type definition
|____features // List of functional components
| |____counter // counter function
| | |____Counter.tsx // counter component
| | |____model // counter model (including state, reducer, computed)
|____models // Other global model definitions
|____configs
```
Enter the project directory and execute `npm i`, and then execute `npm start` to see a default counter page
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/39d54b2f27444598a9ed54f54b494e3c~tplv-k3u1fbpfcp-watermark.image)

> You can also click [here](https://codesandbox.io/s/cra-template-concent-ts-2x347?file=/src/App.tsx) to understand and edit it online.

Of course, integrating `concent` in an existing project is also super simple, because it does not need to provide a `Provider` at the top level, just configure the model in advance.

```js
import {run} from'concent';

run({ // Define a counter model
  counter: {
    state: {num: 1, bigNum: 10 },
    reducer: {
      add(payload, moduleState) {
        return {num: moduleState + 1 };
      },
      async asyncAddBig() {
        await new Promise(resolve => setTimeout(resolve, 1000));
        return {bigNum: moduleState + 10 };
      }
    },
    computed: {
      doubleNum: ({ num }) => num * 2, // This function is triggered only when num changes
    }
  }
})
```
After that, you can plug and play globally. Both class components and function components can use the same way to read data or call methods, click on the key point, **if the ui is a conditional statement to control whether to consume state or derived data If it is, it is recommended to write delayed deconstruction, so that the minimum granularity of the view to the data collected after each round of rendering **
```js
// ###### Function component
function Demo(){
  // If state and moduleComputed are read on demand, it is recommended to write delayed deconstruction
  const {state: {num, numBig }, moduleComputed: {doubleNum }, mr} = useConcent('counter');
  // ... ui logic, binding data, binding method
}

// ###### Class component
const DemoCls = register('counter')(
  class DemoCls extends React.Component{
   render(){
      const {state: {num, numBig }, moduleComputed: {doubleNum }, mr} = this.ctx;
      // ... ui logic, binding data, binding method
    }
  }
)
```

## Introduce concent in next
There is a `_app.js` file in the next basic example directory, which is the root component of the next application
```js
import'../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
```

Because the model must be configured in advance before using `concent`, we only need to create a `runConcent.js` file in advance
```js
import {run} from'concent'
import * as models from'./models';

run(models);
```
Then import it in the `_app.js` file, so that all sub-components under the root component can correctly obtain the store's data and mobilize the store's method.
```diff
import'../styles/globals.css'
+ import'./runConcent'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
```
Then we create a `counter.js` file in the next pages directory, representing that this is a page component, so that the browser can use the `/counter` route to access the rendering view of this component.
```js
import React from'react'
import {useConcent} from'concent'
import router from'next/router'

// use next/router to do browser side router jump
function toHomePage(){
  router.push('/');
}

export default function Counter() {
  const {state, mr, moduleComputed} = useConcent('home')

  return (
    <div>
      this is counter page
      <h1>num: {state.num}</h1>
      <h1>doubleNum: {moduleComputed.doubleNum}</h1>
      <button onClick={mr.add}>add</button>
      <button onClick={toHomePage}>to home page</button>
    </div>
  );
}
```

That's it, a `next` application with `concent` is created. Isn't it particularly simple? ^_^

## Support pre-rendering
`next` provides two levels of pre-rendering interfaces, namely `getServerSideProps` and `getStaticProps`. The difference between the two is the execution timing. `getServerSideProps` is executed every time a page is requested, while `getStaticProps` is executed during construction. Let's deal with the situation of `getServerSideProps` first, and see how to combine `concent` for pre-rendering support.

First of all, we do not consider the existence of `concent`. To do pre-rendering support in `next`, we only need to expose a `getServerSideProps` interface in your page component.

```js
// This function is called every time a page change is requested
export async function getServerSideProps() {
  // Call external API to get the list of blog posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // By returning {props: posts} object, the PostPage component will receive the `posts` parameter when rendering
  return {
    props: {posts },
  }
}

function PostPage({ posts }) {// The posts parameter is received here
  // Render posts...
}

export default PostPage
```

The reason why `Blog` can receive `posts`, in addition to exposing the interface of `getServerSideProps`, let us observe the content of the root component file of `_app.js`, and we can find the key points!

```js
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}
export default MyApp
```

The `pageProps` in the parameter list is the object pointed to by the `props` in the return result of `getServerSideProps`, and then `next` transmits it to the target page component, so we can deconstruct it in the `PostPage` parameter `list. posts`.

So our entry point can start from here, we put the return result of getStaticProps into a format constraint, like a structure like `{module:string, state: object}`, and then record it in the `_app.js` file Go to the store

```js
// This function is called on every request
export async function getServerSideProps() {
  // Call external API to get the list of blog posts
  await delay();
  const posts = [
    {id: 1, name:'post1 -----' },
    {id: 2, name:'post2 --- welcome to use concent' },
  ];
  // This returned object will be transparently transmitted to the pageProps of the root component, where the module and the state entity object to which the state belongs are returned
  // record the status to the store there
  return {
    props: {
      module:'test',
      state: {posts },
    }
  };
}
```

The root component file at this time is changed as follows

```diff
import'../styles/globals.css';
+ import'./runConcent';
+ import {setState} from'concent';

function MyApp({ Component, pageProps }) {
  // Record the return status of getServerSideProps to the corresponding module of store here
+ if (pageProps.module) {
+ setState(pageProps.module, pageProps.state);
+}
  return <Component {...pageProps} />
}
export default MyApp;
```

Then we implemented the page component `post-page` code as follows

```js
const PostList = React.memo(function () {
  const {state} = useConcent('test');
  return (
    <div>
      {state.posts.map(item => <h3 key={item.id}>{item.name}</h3>)}
    </div>
  );
});

const PostLength = React.memo(function () {
  const {state} = useConcent('test');
  return <h1>{state.posts.length}</h1>;
});

export default function PostPage() {
  return (
    <div>
      <h1>this is post page</h1>
      <PostList />
      <PostLength />
      <button onClick={toHomePage}>to home page</button>
    </div>
  );
}
```

Then we open the browser to visit the `/post-page` page, click to view the source code and you will see that this is a server-side pre-rendered page
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ccdd2abc0bab47ff9cf157ae569765b1~tplv-k3u1fbpfcp-watermark.image)

For the same reason, we can also replace `getServerSideProps` with `getStaticProps`, the whole process above will still work normally, you are welcome to see the clone sample code to experience it yourself.

```sh
git clone https://github.com/concentjs/ssr-demo-1
```

## Appendix

### doc
-[next-js doc](https://www.nextjs.cn/)
-[concent doc](https://concentjs.github.io/concent-doc/)

### CloudBase CMS
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d22f440f82d548cc98dd941bf6348e27~tplv-k3u1fbpfcp-watermark.image)

Welcome brothers to pick up [CloudBase CMS](https://github.com/TencentCloudBase/cloudbase-extension-cms) to create a one-stop cloud content management system, which is developed by the cloud and based on Node.js Headless The content management platform provides a wealth of content management functions, is simple to install, easy for secondary development, and is closely integrated with the cloud development ecosystem to help developers improve development efficiency.
> `concent` has provided strong support for its management background, and the new version of the management interface is more beautiful and considerate.

### FFCreator
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8cd7c067bc604123b8d71709be56be8b~tplv-k3u1fbpfcp-watermark.image)

You are also welcome to pick up [FFCreator](https://github.com/tnfe/FFCreator), it is a lightweight and flexible short video processing library based on node.js. You only need to add a few pictures or video clips and a background music, you can quickly generate a cool video clip.
> `FFCreator` is a lightweight and simple solution, it only requires few dependencies and low machine configuration to quickly start working. And it simulates 90% of the animation effects of animate.css. You can easily convert the animation effects on the web page side into videos, which is really awesome.