Serlina: A progressive React serverside-rendering framework
Since Next.js is a lightweight and well-experience serverside-rendering framework, I love using it on my side projects. It saves my time in dealing with the boilerplate code. I write React code, and it just works.
But when I want to use it on a project that runs its own server, something happened. Next.js takes the full control of the http context when app.render
is called, while our server has some effects on the context.
I read the codes of Next.js and try to find something like nextjs/core
but there is not.
So I decided to make a serverside-rendering framework that without running a server. It only does the compiling, rendering your pages to string. And let your own server to render the string to the client. And keep the well-experience just like Next.js.
This is what Serlina is. Let’s see how the most simple Serlina application is like:
npm i serlina react react-dom --save
Create a folder structure like:
├── index.js # Your server
├── page # Your React pages
│ └── page1.js
Your server:
// index.js
const { Serlina } = require('serlina')
const path = require('path')
const http = require('http')
const serlina = new Serlina({
baseDir: path.resolve(__dirname, './')
})
serlina.prepare()
.then(() => {
http.createServer(async (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' })
if (req.url === '/page1') {
const rendered = await serlina.render('page1')
res.write(rendered.string)
} else {
res.write('works!')
}
res.end()
}).listen(8090)
})
.catch(console.error)
Your page:
// page/page1.js
export default () => {
return <div>Hello Serlina!</div>
}
Finally, run node index.js
and visit http://localhost:8090/page1. You will see the React page.
As you can see, there are two main methods:
- prepare() Doing the compiling works.
- render() Render a page and get the rendered string that will be sent to the client.
Unlike Next.js, Serlina needs manually injecting the payload that you use in getInitialProps
:
// index.js
const { Serlina } = require('serlina')
const path = require('path')
const http = require('http')
const serlina = new Serlina({
baseDir: path.resolve(__dirname, './')
})
serlina.prepare()
.then(() => {
http.createServer(async (req, res) => {
+ serlina.injectPayload({ req }) // manually inject. globally res.writeHead(200, { 'Content-Type': 'text/html' })
if (req.url === '/page1') {
+ const rendered = await serlina.render('page1', { foo: 'bar' }) // manually inject. specifically.
res.write(rendered.string)
} else {
res.write('works!')
}
res.end()
}).listen(8090)
})
.catch(console.error)
Then you can use the payload:
// page/page1.js
import * as React from 'react'
export default class Page1 extends React.Component {
static async getInitialProps ({ req }) {
return {
url: req.url
}
}
render () {
return <div>You are now at {this.props.url}</div>
}
}
You can take a deep dive into Serlina through http://serlina.js.org or https://github.com/djyde/serlina
You could use it in your existing codebase easily. Just write a new router, prepare()
before starting your server, render()
a page and return the rendered string to the client. That is. This is why I call it progressive.
Another example is egg-serlina, which is the main reason I create Serlina. It brings the best serverside-rendering solution to Egg.js.