Proof of concept on Pantheon + Deploy on Google Cloud Platform

By the way, Frontity adds a unique hash to all the static files. So itā€™s safe to use an immutable maxage for the cache-control whole /static folder.

Hi @luisherranz, thanks for sharing this progress! Option 5 would definitely work though Iā€™m not sure if Iā€™d rank it above option 3 because Iā€™m looking to get the benefit of Pantheonā€™s CDN. Putting everything behind one domain as far as the CDN and the public is concerned is preferable to me compared to exposing the fact that the static directory is sourced from a some other place.

And the more I think about it, the more I think that I do just need PHP/Theme Bridge to be able to handle multiple backends, so option 3 is fine.

Iā€™m curious in your experience how large the static directory can become for real sites. How large are the largest assets? How big can the overall directory get? Any large image would be an image would be a part of the design/theme, not part of the content of the site, yes?

Let me understand this better:

  1. Is the CDN caching requests of the main domain? (HTML requests)
  2. Or is the CDN only used for static assets in a different domain?

Apart from that, does your CDN honor the cache-control headers?

These are the cache-control headers we recommend for Frontity: https://github.com/frontity/now-builder/blob/master/src/index.ts#L116-L128

    [
      // HTML.
      {
        src: `/.*`,
        headers: { "cache-control": "s-maxage=1,stale-while-revalidate" },
        dest: `/server.js`
      },
      // Static assets.
      {
        src: `/static/(.*)`,
        headers: { "cache-control": "public,max-age=31536000,immutable" },
        dest: `/static/$1`
      }
    ]

We recommend stale-while-revalidate for the HTML for small sites because that way it always serves the cached response, but itā€™s not important for big sites because 99.9% of the visits will get a cached response.

s-maxage is 1 because is a safe default, but of course this should be configured by the site owners, depending on their needs.

For the static folder, we just set it as immutable because all the files include a unique hash.

I donā€™t quite understand this. Point 5 does precisely that: it puts everything under the same domain. Remember that when the PHP Theme Bridge is in charge, it does an internal HTTP request and returns the response, so everything that goes through it will use the WordPress domain.

Maybe we need some diagrams with all the scenarios to make sure weā€™re talking about the same cases :slight_smile:

A Frontity server should always remain quite small.

The current server size is 1Mb, including React, Koa, the Frontity core and the rest of the libraries required. I guess the most complex app possible shouldnā€™t get bigger than 3Mb.

The client assets are a bit smaller, 600Kbs. If we embed them in the server, itā€™d be 1.6Mb. I guess a very complex app with client assets embedded could be 5Mb, which is still a tolerable size for serverless.

Adding fonts or an image for the logo is fine. But to store images included in the WP content, they should use their WordPress site, not Frontity.

But they shouldnā€™t doesnā€™t mean they wonā€™tā€¦ Maybe, if people start doing it we will need to teach them that they should use WP for that.

Is option 3 using the publicPath option or doing the routing with your own proxying logic?

I donā€™t quite understand this. Point 5 does precisely that: it puts everything under the same domain.

@luisherranz Ah, yes, I definitely misread what you had in mind for point 5. Sorry about that!

Maybe we need some diagrams with all the scenarios to make sure weā€™re talking about the same cases :slight_smile:

Yes, :smile: this is what I had in mind for point 3, and I think it is also might be what you were imagining for point 5.

Pantheonā€™s CDN does respect cache-control headers and it caches both static assets and HTML responses with headers designating them as cache-able.

Thanks for sharing the recommended cache-control headers from the now-builder example. That looks like Now-specific configuration. Is there a way to get the Frontity server to emit those headers?

A Frontity server should always remain quite small.

Cool, those numbers align with what I expected.

Hey, Iā€™m glad we are on the same page. Thanks for the diagram, that was exactly what I meant :slight_smile:

Frontity doesnā€™t add any headers right now. It could add default headers,

Server extensibility, which is currently on the roadmap, will allow any Frontity package to extend the underlying Koa* server.

For example, this will add the recommended cache-control headers:

export default {
  // Your normal package stuff: state, actions, React...
};

// Extend Koa server.
export const server = ({ app }) => {
  app.use(ctx => {
    if (ctx.path.startsWith("/static/")
      ctx.set("cache-control", "max-age=31536000,immutable");
    else
      ctx.set("cache-control", "s-maxage=1,stale-while-revalidate");
  });
};

We also have in mind to create an official package for this. Iā€™m not sure if @frontity/cache-control or a more general @frontity/headers.

People will be able to configure this @frontity/cache-control package in their Frontity settings.

  • With the default settings:
const settings = {
  packages: [
    "@frontity/cache-control",
    // ...
  ]
};
  • With custom settings:
const settings = {
  packages: [
    {
      name: "@frontity/cache-control",
      state: {
        headers: {
          ssr: "s-maxage=300"
        }
      }
    }   
  ]
};

*We choose Koa over Express because itā€™s half the size, easier to use and donā€™t include dynamic imports (bad for bundling it in a single server.js file).

Awesome :smile:

@stevepersch I have a question for you.

What is the benefit of using a proxy over just replacing the PHP theme?

I ask because if you are going to cache the final HTML anyway, the performance gain is going to be minimal and with that type of proxy some of the things that usually work in WordPress are going to break: direct access to PHP files used by some plugins, plugins that add custom files/routes like sitemaps, robots or ads.txt, plugins for 301 redirections, plugins that modify headersā€¦

My idea was to use template_include to overwrite the current theme and wp_remote_get for the request so Iā€™d love to hear your opinion on this matter :slight_smile:

Hi @luisherranz!

Are you asking about doing the proxying in a CDN instead of in PHP? Yeah, Iā€™ve been working on the assumption that developers will be skeptical of proxying in PHP because they know itā€™s faster and more scalable to do so in a CDN. But youā€™re right that cache-hits make the difference negligible in most situations.

For me, itā€™s less about specific technical benefits as it is about the mental model. In the ideal diagram, would Frontity be inside of WordPress or next to WordPress?

I think Iā€™ve been mentally drawing the ideal diagram with Frontity next to WordPress. But maybe in the ideal diagram, Frontity would be inside WordPress, replacing the theme. The fact that getting the HTML from Frontity requires calling out to a separate Node.js environment could be abstracted away similar to the way an object cache API abstracts away the detail of whether your cache objects are coming from PHP memory, a database, or Redis.

Putting Frontity inside WordPress would be less disruptive to existing WordPress sites. It could allow plugins to alter responses and do things youā€™re pointing out like sitemaps, etc (although even when Frontity is outside WordPress there would need to be configurable logic for which paths go to which runtime, PHP or Node)

Putting Frontity next to WordPress might be a clearer mental model for newcomers. I think it would allow front-end developers to have a better understanding of what is happening where compared to a model where PHP has the potential to alter the output.

Another very tangible version of this question is ā€œone repo or two?ā€ As youā€™ve seen, the sample projects Iā€™ve done with Frontity combine the repos. Frontity becomes a directory inside the WordPress codebase and deployments to PHP and Node.js happen in the same deployment pipeline. But would teams prefer two repos with two deployment pipelines?

Either model can be optimized for performance. Which model is better for optimizing understanding?

Yes, those are precisely our thoughts on the matter and the reason I asked you :slight_smile:

  • If Frontity is next to WordPress then we are talking about Headless WordPress.
  • But if Frontity is inside WordPress then we are talking about an alternative React rendering engine for WordPress.

Both approaches are very interesting but the second one is more natural to WordPress. So we think it will make more sense for hostings like Pantheon, which are already taking care of the CDN/caching of WordPress output gracefully. But please give it a thought and let me know.

We plan to support both architectures by the way.

I donā€™t think the mental model of the PHP Theme Bridge will be hard to understand. It may be even easier for those not familiar with the Headless CMS architecture.

I guess thatā€™s up to each team to decide. But in my opinion, having both codebaes in the same repo makes sense because, as you said in an early message, sometimes a new feature will contain changes in both WP and Frontity that must be tested and deployed simultaneously.

1 Like

Hey @stevepersch, I hope you are doing great in these uncertain times :slight_smile:

I just wanted to share with you a proof of concept of the Theme Bridge:

The implementation is so simple that it doesnā€™t make much sense to do an explanation. Just take a look at the code :slight_smile:

The only problem Iā€™ve stumbled upon so far was that the normal Nginx configuration for WordPress doesnā€™t send static file requests (like js, fonts, imagesā€¦) to WordPress, it just returns a 404 if it doesnā€™t find those assets in the file system.

To solve that, people would have to either:

  1. Change their Ngix configuration.
  2. Use a different publicPath setting to request the static assets directly from their Node server (or static storage).
  3. Remove extensions from Webpack. For example, file.js becomes file--js.

But we donā€™t like this second idea because that means they have to configure an additional cache/CDN for that URL and we donā€™t like the third idea because, wellā€¦ it feels really hacky and incompatible with any other system that expects files to have proper extensions.

Iā€™d love to know you solved the Nginx config problem in Pantheon. I guess you already had that problem before with the HackyProxy, hadnā€™t you?

@SantosGuillamot is going to open a Feature Discussion here in our forum to start talking about the possible features and configurations.


Apart from that, I forked the simple-cache plugin to add the Content-Type header of static assets.

I must say that I am amazed by the performance. Itā€™s faster than I thought. Once itā€™s cached, my local server answers in about 5ms for both the HTML and static assets from Frontity. Itā€™s not something you need for Pantheon, but Iā€™d like to mention it as well :slight_smile:

Hey @luisherranz,

Sorry for the delay here, Iā€™m holding up!

On Pantheon, we hit that nginx limitation you describe on woff2 files but luckily not js, css, or images. I think thatā€™s because Pantheon was first built for Drupal which sometimes creates those files on-demand.

To accommodate the ā€œFrontity inside of WordPressā€ case, weā€™ll need to alter nginx or sync those assets to an unversion-controlled directory on the PHP container.

Would you be open to a pair programming session later this week where we could try to set up theme-bridge-poc on the Pantheon+GCP Cloud Function architecture?

Absolutely :+1:

Iā€™ll send you an email.

To summarize our meeting: The Theme Bridge PoC plugin is working great in Pantheon :tada:

The instant cache invalidation of Pantheon is working great with this approach, both for the HTML and the REST API requests.

Next steps:

  • Frontity
    • Add build-time configuration of publicPath.
  • Pantheon:
    • Find a way to upload the /static folder to the WordPress file system after running npx frontity build in the CI.

Once we have those things, we can test a real site in their infrastructure.

After that, we can work on other not so critical issues:

  • Frontity
    • Release the final Theme Bridge plugin with support for using ENV variables to change the settings.
    • Add dynamic configuration of publicPath.
  • Pantheon
    • Create an upstream of WordPress+Frontity.
3 Likes

I have added a Feature Discussion for the publicPath and I gues we will include it in the next sprint: Change publicPath.

Can you @SantosGuillamot confirm that?

We havenā€™t finished planning what weā€™re going to do the next sprint but this is one of our top priorities so itā€™ll be probably included yes.

1 Like

Finally got a chance to play with this POC and Iā€™m frightened by how well it worked. Likeā€¦ it just works - I was not expecting that. This is on 2 separate Digital Ocean instances (one nginx and one node jobby).

I just popped it on my WP instance and it pulled the JS from the separate external node machine. Awesome.


(medusa.403page.com is the hostname for my backend for 403page.com - for this test you can see itā€™s serving both)

Testing on WP Engine next.

1 Like

Confirmed working nicely on WP Engine (nginx).

Itā€™s a valid point about the /static/ folder. Got 404ā€™s out of the box as expected. But I added this nginx rule easily enough to make it work:

# Serve /static from node server through main domain
location ~* ^/static/?(.*) {
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Cache-Control $http_cache_control;
    proxy_pass [http://MY_NODE.JS_SERVER_IP:3000];
}

Itā€™d be very cool if that could be handled inside PHP - but Iā€™m not sure how/if that would work yet. Gonna play around a bit and seeā€¦

P.S. @stevepersch is that nginx rule easy enough to implement on Pantheon on a per user basis? Might be a good first step for now at least.

@403page if Iā€™m not mistaken, the approach in Pantheon is going to be changing Webpackā€™s publicPath to use a different /static folder.

Imagine you have a folder structure like this:

/var/www/html
  /wp-includes
  /wp-content
  /frontity
    /build
      /static
  ...

Then you would change the publicPath to "/frontity/build/static" and the URL of your scripts/fonts will become:

https://yourdomain.com/frontity/build/static/my-assets.some-hash.js

If the Frontity folder is instead inside wp-content, like this:

/var/www/html
  /wp-includes
  /wp-content
    /frontity
      /build
        /static
  ...

Then, then publicPath would be "/wp-content/frontity/build/static" and the URL:

https://yourdomain.com/wp-content/frontity/build/static/my-assets.some-hash.js

That way, Nginx can find the files in the file-system and everything works fine.


Our current idea is to add the ability to change the publicPath in build-time:

> npx frontity build --publicPath="/other/folder"

or in run-time, to be able to store the setting in WordPress:

https://mydomain.com/some-post/?frontity_publicPath=/other/folder

More info on the Public Path feature discussion. Feel free to give us your feedback :slight_smile:

Thanks for clarifying that @luisherranz.

Gotcha. I was struggling at first to see the workflow where youā€™d deploy /static/ to the Wordpress host and the rest of the site on the Node instance - but that helps a good deal. It would be easy enough to automate that anyway.

I think I was coming from a place of oversimplifying. Install the plugin and add the nginx rule one time on the WP instance and then deploy/update Frontity the external node.js server as normal.

The Public Path feature is definitely very interesting. Heading over to that thread nowā€¦

Well, changing the Nginx configuration is going to be always possible, and that problem doesnā€™t seem to be present on Apache. But we want to provide a method for those who donā€™t want (or donā€™t know) to change their Nginx configuration. So far, changing the publicPath seems to be the best option, although we are open to other ideas :slight_smile:

1 Like

Yeah, to echo what @luisherranz said, Iā€™d like to try a configurable publicPath before making nginx config changes. Pantheon standardize nginx config across all sites. In my R&D work right now Iā€™m sometimes going outside what our normal platform does, but ultimately I want to find a way to support Frontity that can minimize changes to our infrastructure and workflow.

2 Likes