spatie / server-side-rendering

Server side rendering JavaScript in a PHP application
https://sebastiandedeyne.com/posts/2018/server-side-rendering-javascript-from-php
MIT License
602 stars 34 forks source link

[Question] First byte drastic performance decrease #18

Closed jkujawski closed 6 years ago

jkujawski commented 6 years ago

In e-commerce site that heavily uses Vue as a javascript framework we've faced the problem of generating full source for SEO purposes.

We have decided to try and go with SSR.

Unfortunately after enabling SSR performance of website drastically decreased. Without SSR we have ~400ms response time (first byte) for a website that has to analyze ~250k offers, filters, and other stuff. Once we enable SSR first byte jumps to around ~1.2s. Which unfortunately is not acceptable for the SEO purposes.

We use Node engine, which basically means that on each request new node instance is started.

We can of course try to apply caching, and not remove generated .js file for some time and solve this issue for uses, but this won't solve the problem for robots. Because if a page is not cached, then first byte will still be around ~1.2s

Any ideas what might be done to improve performance of SSR?

robinvdvleuten commented 6 years ago

What I've done to increase performance is to wrap the Node.js renderer around a custom caching class (webstronauts/laravel-liftoff/app/Ssr/Engines/Cached.php). it uses the md5 hash of the script as caching key, so any dynamic context is taken into account.

robinvdvleuten commented 6 years ago

It gives me a response time of ~60ms on Heroku, including session resolving.

jkujawski commented 6 years ago

Thanks @robinvdvleuten

We are thinking about implementing it as well. But as I mentioned in my question this would improve the performance on consecutive requests. In my case, for SEO purposes the first request is most important. Because it has 1,2s now. Once it's cached it will be fast, as you said. But we cannot cache automatically whole website, because we'd have to crawl whole website and cache it.

But thanks for your feedback. We'll be analyzing your solution.

robinvdvleuten commented 6 years ago

@jkujawski I guess you always would have such issues even without using SSR; the cache has to be filled somehow.

sebastiandedeyne commented 6 years ago

First of all, thanks for the feedback! This is exactly the reason why this package isn't tagged 1.0 yet, because the tradeoffs are still a bit unknown and I haven't put a lot of thought in performance yet.

A while ago I came across this library: https://github.com/chancedickson/isorender-node

Instead of starting up a new Node process in every request, something similar to the Isorender package would allow you to spin up a Node process in the background, and commuicate with it via localhost. I think this would be a huge perf boost compared to the basic Node implementation supplied here.

It should be possible to write a custom Engine implementation that can communicate with an external process. I'd love to have this in the package at some point (maybe with the required JS for the process included instead of relying on a third party here), but haven't gotten the chance to experiment with this yet.

jkujawski commented 6 years ago

Thanks @robinvdvleuten @sebastiandedeyne

We will try to investigate isorender-node.

As for cache, of course we are going to use it. But at this point we are mostly focused on the first byte of first page hit. Because there's a chance our users won't find every landing page, and google bot while parsing the page will for sure.

I'll let you know once we have any findings regarding the isorender-node.

sebastiandedeyne commented 6 years ago

As for cache, of course we are going to use it. But at this point we are mostly focused on the first byte of first page hit. Because there's a chance our users won't find every landing page, and google bot while parsing the page will for sure.

On my personal site I run a crawler over all pages after every deploy. Totally different project scale of course, but if this is also an issue cache warming could also give a helping hand!

https://github.com/sebastiandedeyne/sebastiandedeyne.com/blob/470017a2acfe1ccbb581aec9b80abc6e1a47669f/app/Console/Commands/WarmCommand.php

jkujawski commented 6 years ago

@sebastiandedeyne we are currently discussing this approach internally. Unfortunately we have like hunred of thousands of landing pages. Or more. Warming cache in this case will be extremaly difficult and time consuming.

sebastiandedeyne commented 6 years ago

Gonna close this. Feel free to continue the discussion!

joshhornby commented 5 years ago

@robinvdvleuten The link you posted in this comment has stopped working. Can you please share your caching code in this issue?

Thanks

robinvdvleuten commented 5 years ago

@joshhornby sure! I'll put it here for further reference;

<?php

namespace App\Ssr\Engines;

use Illuminate\Support\Facades\Cache;
use Spatie\Ssr\Engine;
use Spatie\Ssr\Engines\Node;

class Cached implements Engine
{
    /**
     * @var Node
     */
    private $engine;

    /**
     * Create a new engine instance.
     *
     * @param Node $engine
     */
    public function __construct(Node $engine)
    {
        $this->engine = $engine;
    }

    /**
     * {@inheritdoc}
     */
    public function run(string $script): string
    {
        // Use the file cache to store the result for 24 hours. This cache get cleared upon a new
        // deployment as Heroku's file storage is ephemeral. The script's md5 hash contains
        // the passed context so you can safely cache "user-specific" renders.
        return Cache::store('file')->rememberForever(md5($script), function () use ($script) {
            return $this->engine->run($script);
        });
    }

    /**
     * {@inheritdoc}
     */
    public function getDispatchHandler(): string
    {
        return $this->engine->getDispatchHandler();
    }
}
dyegonery commented 5 years ago

I'm also facing this issue currently. Have you tried using V8Js as the engine? Is the performance with V8Js also an issue?