Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discussion] Using Rapscallion with NextJS #2279

Closed
wants to merge 1 commit into from

Conversation

gcpantazis
Copy link
Contributor

@gcpantazis gcpantazis commented Jun 15, 2017

This adds support for, and an example of, using Rapscallion (or
potentially other alternate React renderers) with Next. This has been
discussed in #1334 and #1209. I'm submitting this as a proof of
concept for discussion, though the changes are fairly minimal. I'm
hoping with some bit of feedback we can add this for real!

The issue as mentioned by @arunoda elsewhere is that document.js
uses renderToString() directly, which makes it difficult to inject an
alternate renderer method.

What I did here:

  1. creating a renderToParts() method, which in turn calls doRender with
    added configuration to allow for the replacement of renderToString,
    and keeping renderToString as the default.

  2. Jettison document.js, and in the example server.js use
    Rapscallion templating to effectively do the same thing.

  3. 🎉🎉🎉!

The main issue this raised:

  • The subcomponents of Document (Head, NextScript) rely on
    context, and thus are difficult to interface with except through
    Document. I got around this by falling back to props when context was
    not defined. Hacky of course, and probably is the main thing we'd need
    to refactor to do this "right".

Results:

The test I created for examples was to generate a MD5 hash for the first
3000 integers. Since this is deterministic, I cached it with
Rapscallion. renderToString continued to render it as normal on
baseline. The test run was with apache benchmark, ab -n 500 -c 10.

Baseline:

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.4      0       4
Processing:   239 1293 246.1   1253    2099
Waiting:      238 1291 245.8   1251    2099
Total:        240 1293 246.1   1254    2100

Percentage of the requests served within a certain time (ms)
  50%   1254
  66%   1337
  75%   1425
  80%   1444
  90%   1588
  95%   1886
  98%   1981
  99%   1981
 100%   2100 (longest request)

With Rapscallion:

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.3      0       4
Processing:    97  204  39.6    190     318
Waiting:       97  202  38.6    189     318
Total:         98  205  39.6    191     319

Percentage of the requests served within a certain time (ms)
  50%    191
  66%    205
  75%    224
  80%    235
  90%    269
  95%    307
  98%    315
  99%    315
 100%    319 (longest request)

... or a roughly 6-7x improvement. Not super-surprising, since caching,
but for us a larger portion of each page is static, or at least made up of
pure components with predictable I/O — being able to cache those,
while leaving other areas to be completely dynamic, is a huge win.

I played around with Rapscallion's streaming rendering as well, but I
think that'll be most useful with something a little more real-world.
Streaming seemed to have a small perf hit that gradually lessens the
larger the CPU effort / payload becomes. If you were adding a bunch
of CSS output in your header, for example, I think streaming would
greatly improve load time.

So yeah, open to suggestions here. I think this is a good stab at a
general interface for getting "parts" for a more atomic Next render,
but the API of that could certainly be better. Looking forward to
hearing what you think!

Love,
@gcpantazis + @thumbtack ❤️

This adds support for, and an example of, using Rapscallion (or
potentially other alternate React renderers) with Next. This has been
discussed in vercel#1334 and vercel#1209. I'm submitting this as a proof of
concept for discussion, though the changes are fairly minimal. I'm
hoping with some bit of feedback we can add this for real!

The issue as mentioned by @arunoda elsewhere is that `document.js`
uses `renderToString()` directly, which makes it difficult to inject an
alternate renderer method.

**What I did here:**

1. creating a `renderToParts()` method, which in turn calls doRender with
added configuration to allow for the replacement of `renderToString`,
and keeping `renderToString` as the default.

2. Jettison `document.js`, and in the example `server.js` use
[Rapscallion templating](https://github.com/FormidableLabs/rapscallion#template) to effectively do the same thing.

3. 🎉🎉🎉!

**The main issue this raised:**

* The subcomponents of `Document` (`Head`, `NextScript`) rely on
context, and thus are difficult to interface with except through
`Document`. I got around this by falling back to props when context was
not defined. Hacky of course, and probably is the main thing we'd need
to refactor to do this "right".

**Results:**

The test I created for examples was to generate a MD5 hash for the first
3000 integers. Since this is deterministic, I cached it with
Rapscallion. `renderToString` continued to render it as normal on
baseline. The test run was with apache benchmark, `ab -n 500 -c 10`.

Baseline:

```
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.4      0       4
Processing:   239 1293 246.1   1253    2099
Waiting:      238 1291 245.8   1251    2099
Total:        240 1293 246.1   1254    2100

Percentage of the requests served within a certain time (ms)
  50%   1254
  66%   1337
  75%   1425
  80%   1444
  90%   1588
  95%   1886
  98%   1981
  99%   1981
 100%   2100 (longest request)
```

With Rapscallion:

```
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.3      0       4
Processing:    97  204  39.6    190     318
Waiting:       97  202  38.6    189     318
Total:         98  205  39.6    191     319

Percentage of the requests served within a certain time (ms)
  50%    191
  66%    205
  75%    224
  80%    235
  90%    269
  95%    307
  98%    315
  99%    315
 100%    319 (longest request)
```

... or a roughly 6-7x improvement. Not super-surprising, since caching,
but for us a larger portion of each page is static — being able to
cache that, while leaving other areas to be dynamic, is a huge win.

I played around with Rapscallion's streaming rendering as well, but I
think that'll be most useful with something a little more real-world.
Streaming seemed to have a small perf hit that gradually lessens the
larger the CPU effort / payload becomes. If you were adding a bunch
of CSS output in your header, for example, I think streaming would
greatly improve load time.

So yeah, open to suggestions here. I think this is a good stab at a
general interface for getting "parts" for a more atomic Next render,
but the API of that could certainly be better. Looking forward to
hearing what you think!

Love,
@gcpantazis + @thumbtack ❤️
@arunoda
Copy link
Contributor

arunoda commented Jun 16, 2017

This is pretty great.
I'd like to work on these(or take this directly) as soon as after we ship 3.0

@timneutkens timneutkens added this to the After 3.0 milestone Jul 1, 2017
@danoc
Copy link

danoc commented Aug 7, 2017

Does React 16's improvements to SSR change the approach here?

@gcpantazis
Copy link
Contributor Author

@danoc Yep, moved all this to #4074 😄(finally)

@lock lock bot locked as resolved and limited conversation to collaborators Mar 29, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants