Skip to content

Latest commit

 

History

History
55 lines (34 loc) · 11 KB

WhenToUse.md

File metadata and controls

55 lines (34 loc) · 11 KB

When To Use

On contrary to the title of the section, let’s start with “When not to use it”:

  • You are looking for run-time SSR because the content of your landing page changes frequently throughout the day. For example, an e-commerce shop needs to show constantly changing specials. Or the landing page should reflect user's purchasing history. In such a case this solution would be suboptimal, it implements build-time SSR also known as prerendering or static generation.

    The scenario described above would be best served by one of the run-time SSR frameworks. The sole reason for that could be the landing page performance. If prerendered into HTML at build-time, the landing page would need two downloads performed one after another: download the prerendered page along with its script and then the script would fetch data to be used for additional client-side rendering. Run-time SSR allows to fetch data and generate the final HTML at the server, then send it to the client so that only one download is required. This benefit could evaporate for non-landing pages as explained in the Performance section.

  • Your React/NodeJS/TypeScript knowledge corresponds to a novice level. On the other hand, you don’t need to be an expert in any of those fields in order to use Crisp React.

  • You are looking for a framework with built-in functionality. Crisp React is not a framework, it's a slim production-ready boilerplate that comes with only few libraries included. For example, Semantic UI and Emotion CSS. You can replace these libraries and/or add others to implement the features you need.

Now we can switch back to "When to use".
Crisp React can help to achieve top security and performance along with SEO, simplicity and maintainability. It makes most sense when you strive to develop a project tailored specifically to your needs and consider frameworks with lots of pre-baked functionality to be a liability rather than an advantage. This approach assumes you want to choose third-party libraries/packages as to not reinvent the wheel and write the code utilising those to avoid (or keep at the bare minimum) the lowest-common-denominator implementation meant to suit everyone and exposed to hackers.

This solution would be beneficial if one or more of the cases described below apply to your situation. The title of each case describes a concern or need you might have.

Case: Build and Deployment Simplicity

Your goal is a simple project with a straightforward client build coupled with no-hassle and free Jamstack deployment. At the end you would like to get a reasonably polished website (with good test scores) served to end users by the server provided by a Jamstack provider.

You used online tools (e.g. web.dev/measure, webpagetest.org) to obtain the test scores for the website jamstack.winwiz1.com which is deployed automatically from Jamstack builds. Then you went through the Cloudflare Pages section to create your own site that doesn't need multiple SPAs so you have used the SPA configuration block shown there. You completed the section noting that it took around a minute to copy the configuration data provided in the section and paste it into the screen presented by Cloudflare. In response, Cloudflare had built and deployed the site. Finally you used the same online tools to get similar test scores for the newly built website.

Case: Run-time Complexity

You have examined an opinionated SSR framework. It revealed significant run-time complexity which you quantified by the depth of the call stack that needs to be looked at while investigating a possible production issue. To be more specific, let’s investigate a simple hypothetical problem with browser’s requests for a React script bundle causing errors with the status code 404 Not Found.

  • For Crisp React, you found the node ./build/srv/main.js command (that starts the backend) in the package.json file. This lead you to the relevant source file and the line of code. It increased the depth of the call stack from ‘0’ to ‘1’ while pointing to the code in this file. You identified the route handler and when it gets executed, the call stack depth increases from ‘1’ to ‘2’. The handler is a thin wrapper around Express middleware. The middleware is called either directly, growing the call stack depth to '3', or indirectly via another helper function thus increasing the call stack depth to '4' at the point of time the Express middleware is executed. You assumed it's very unlikely that the issue at hand could be caused by either Express code or its stock middleware. Which restricted the scope of your troubleshooting effort to just 3 call stack levels with each level represented by a custom (e.g. non-Express) function.

  • For the opinionated framework, you ran a CLI command to create a standalone project and found a simple command in the generated package json file that runs the server. However the command doesn't call node, it invokes the framework itself. You know that Express is used under the hood so you attempt to follow the call stack and find its depth at the point of time when the Express code or stock middleware is called. The task could take quite a while (unless you put a breakpoint in Express code) and can be abandoned once it becomes clear the call stack is deeper than you are inclined to accept without the requirement to support run-time SSR.
    In other words, supporting run-time SSR is a complex task that requires complex code and the framework copes with this task very well. However, using the same complex machinery in cases when build-time SSR is sufficient results in an overkill.

Apart from maintainability/troubleshooting challenges, you know that on the one hand each call stack level (and the function it represents) usually expects some input data. On the other hand, maliciously crafting requests beyond of what is expected by the code on the receiving end is a known hacking technique. You appreciate that generally speaking, complexity doesn't help with security.

Case: Vendor Lock-in

You work on a project and one of the key requirements is to avoid vendor lock-in. You shortlisted this solution because it doesn't require a developer to consistently use any special programming constructs (like component properties or data fetching hooks specific to Crisp React) throughout the code.

You noted the SPA configuration file is solution-specific and so are the entrypoint files located under src/entrypoints. However the rest of the codebase is portable and so will be the components that you intend to write.

Case: Enterprise Requirements

You work in an enterprise and need to protect Intellectual Property e.g. the business logic expressed in the React code. You'd like to have a separate Login SPA rendered by its own script bundle and make other bundles available for download to authenticated users only.

You plan to reuse this approach to add an additional layer to the existing RBAC (Role Based Access Security) e.g. make some bundles available depending on certain roles attested in the JWT token.

You conclude that adding this functionality to the React application split into multiple SPAs will be easy in both theory in practise. For full stack builds, this could be as simple as adding a middleware to Express. For Jamstack, the functions running on the CDN edge can pick up this work.

You have a complex website and would like to parallelize development among several teams. So that each team can work on a separate unit of work, more specifically a unit of planning, development, testing and acceptance by PM. The conclusion here is that each SPA fits naturally into the definition of such a unit since each team can leave only the SPA under development in the SPA configuration block.

Case: Enterprise Grade Security

The project you are engaged with requires top-notch security. You are focusing on Application Security and have implemented the whole battery of protective measures. That includes scrutinising the API requests before the actual processing can start. Various metrics are checked and the request size is on the list. You planned to use Jamstack build provided by this solution to create SRI compliant build artifacts, store those in a cloud bucket and let a CDN handle the requests. The API requests would be handled by API Gateway. The gateway would check the body of API requests and start cloud functions. The functions written in JS would also scrutinise the API requests by checking HTTP headers.

However SO (Security Officer) is not impressed. She points out that good security is always multilayered with repetitive/redundant measures duplicating each other. SO rules the restrictions on the request size and rate limiting must be additionally implemented at yet another layer along with some networking timeouts/limits. SO stipulates the code performing all those checks must be compiled rather than written in an interpreted language like JS.

Backgrounder. The JS code is executed by the V8 engine. It compiles JS into CPU instructions at run-time further optimising the compiled code, again at run-time. The compilation is more or less predictable but the optimisation is not because in order to achieve the best performance it depends on run-time inputs. This lack of predictability due to non-deterministic code optimisation makes a comprehensive security audit of V8 impossible and results in vulnerabilities including zero day ones.

You comply with the additional security requirements by using Nginx. It's available as a Docker image. The API Gateway can start containers instead of functions so you switch from Jamstack build to full stack build that produces a Docker image. However the container management, including health monitoring, looms as a big issue. The API Gateway can monitor health provided the containers are modified to support a custom API used specifically for monitoring.

Now PM steps in and says you need to avoid vendor lock-in. That makes you consider Kubernetes. You conclude that k8s clusters, scaled to handle API requests from end users, will barely notice a much lighter workload of serving React app build artifacts to the CDN datacenters. You note this is exactly what the full stack build does - it uses a single server to combine the frontend and backend roles. Which makes the cloud bucket unnecessary and helps to improve maintenance by keeping the code responsible for HTTP headers management largely in one place. This architecture gets a nod from SO who is additionally pleased with security not being watered down by using CORS.


Back to the README.