Photo by Duy Pham on Unsplash

When I first heard JAMstack, or “static app” over a year ago, I was confused. What does it mean to be “static”? Does it take “dynamic” server data and “dynamic” user input? I made my biggest courage and threw our these questions in our internal slack channel. Our then architect Jamund Ferguson kindly helped clarify:

It makes some sense to me then. “Static” here refers to static assets, i.e. javascript, css, markup, rather than a dumb static page. But wait, isn’t that what we have been doing all the time? Isn’t that all Web apps consist of static assets? Look at Javascript, API, Markup, nothing appears new. How come JAMstack suddenly become a thing? What is new about it?

The JAMstack conference 2019 in San Francisco I attended last week, as well as the book Modern Web development on the JAMstack by Mathias Biilmann and Phil Hawksworth finally gave me the answer.

Monolithic structure

The value of JAMstack is much easier to understand when comparing to monolithic architecture, today’s most common practice in Web development. In a monolithic project, the frontend is closely coupled with server, e.g. node.js. Each page request must go through server, which pulls data from database, renders into html, and sends it across network.

Apparently there is room for improvement here. Why repeatedly build a page for each and every request, even when the page is identical? Can we generate the html once and serve it to all requests?

That’s exactly what JAMstack does! In a JAMstack architecture, a page request does not hit a server for html. Instead, it fetches html from CDN, where an html file has been pre-built and is ready to be downloaded. No server is involved in the process.

When I met Kyle Mathews in the JAMstack conference, he had a short, good answer to the question “why JAMstack?”: because it is faster, easier, and cheaper.



- Faster. JAMstack is faster because html has been prebuilt, thus avoiding all the overhead of querying data from database and processing into html at runtime. And the prebuilt html sits at CDN which is closer to user, it can be downloaded much faster. Finally, since the html is simply a static file corresponding to a URL, it can be cached as long as no new version is deployed.

- Easier. Frontend developers only need to worry about the UI part, rather than “full stack” ranging from UI to server to database. Also with help from tooling built around JAMstack, it is much easier to deploy a static app.

- Cheaper. No server is needed. CDN is efficient at serving files. And because it only handles “reading” operation and no “writing”, it can serve a lot more requests concurrently. We don’t need to worry about load capacity of server or database during traffic peak. Horizontal scaling made easy.

Is JAMstack something new?

Again, if it is only J.A.M, nothing seems new in the stack? Isn’t that we were doing back in the old dark days?

Kind of true. JAMstack is not a new technique per se. I really like the definition of JAMstack given in Mathias and Phil’s book:

…[JAMstack] is not any one specific technology in and of itself; nor is it driven by a large company; nor is there any standards body that controls or defines it.



Instead, the JAMstack is a movement, a community collection of best practices and workflows that result in high-speed websites that are a pleasure to work on.

Just like many other things that go in a cycle, JAMstack is still about static js/css/html, but it brings way better developer experience and robust app, thanks to a bunch of tooling that did not exist before. It is the underlying tooling that makes JAMstack new. Let’s look at some of the paradigms and technologies that have enabled JAM stack to become more viable.

Instant cache invalidation at CDN

While CDN has been utilized for a long time for static asset serving, it used to be blamed for its unpredictable behavior in invalidating cache. Imagine when you deploy a new version of Web app, users are using different versions depending on how assets are cached. This could easily lead to unexpected bugs. Netlify claimed back in 2015:

Except for Netlify, all CDN hosting services for static sites (that actually cache sites) make you wait anywhere from 10 minutes to several hours for your changes to be invalidated. So in other words, once you publish a change in your site, you have to wait to see it live.

Caching issue at CDN was largely why a monolithic architecture is preferred. In a monolithic structured app, html is dynamically rendered for each request and thus does not get cached at CDN at all.

Being able to instantly invalid cache is critical for JAMstack. Netlify talks about how they make it possible. Essentially (which means I don’t fully get it myself 👻. I’m only talking about my understanding, which might be limited or wrong. The same applies to the whole post), they set cache control in http headers in a way that browser caches the content but checks with CDN every time if a new version is available. The response header includes a field etag which is a version hash. The browser then sends it back every time it tries to refetch the resources. If the etag matches, it means no update has happened and thus no download is needed. In contrast, if etag does not match, the browser will download the new version. With every app deployment, CDN updates etag , thus telling the browser to abandon the cache and fetch the new version.

CORS

Browsers enforce same-origin policy for security reasons. That is, a web app can only request resources from the same origin the app was loaded from. In that case, a monolithic structure makes sense because an app must have a server from the same domain to provide data.

CORS (Cross-Origin Resource Sharing) is developed to relax the restriction. When a web app tries to request from a different domain, the server checks the origin of the request. If allowed, it appends its origin to Access-Control-Allow-Origin in the response header. When the browser receives the response, the browser checks the header to see if it matches the current domain. If so, the app is able to read the response. Otherwise, the response is blocked.

CORS opens up possibility to provide services from third party. Most public APIs today support CORS. For example, Amazon S3, YouTube, and Stripe all turned on CORS around 2012. Being able to request data from other domains frees JAMstack apps from maintaining its own server. Apart from the frontend team, you can have another dedicated team to develop API. Or you can just use third party APIs without a server at all!

JWT

Most often, data access from API needs to be authenticated. Traditionally, auth is done with session. As the user logs in, the server creates a session id and saves it along with user information in database, or a key value store like Redis, or server memory. The session id is then sent back to browser. For subsequent requests from the client, the session id is sent along to server and checked against the one saved in the server side. If they match, the request is authenticated and user info is attached for further data fetching. In summary, with the session approach, a server side storage (database or key value store or server memory) is required. If the client side wants to consume multiple services from multiple servers, these servers must share access to the same storage. This, however, makes servers less modular. A monolithic architecture works better with session as a single server handles auth for the client.

JWT, or JSON Web Token, in contrast, can authenticate a user without database. When user logs in, the server creates a JWT with a secret. Compared to session which is only an id, JWT can save public user info in the token. The JWT is sent back to browser and stored in browser. Subsequent requests will have JWT in the http header, and server with the secret is able to verify whether the sender of JWT is who it says it is and whether info in the JWT has been changed. Because user info is stored in JWT in client, and all that server saves is a secret (no database or store needed), the client is able to have authenticated access to all services that share the secret. In other words, the client can have personalized service from third party APIs.

Open API and microservices

With power of CORS and JWT, it opens up a whole new world of API based services. You don’t need to create your own server for authentication or providing other service/data, an app with a signed token can access any API, whether it is public or authenticated.

Today pervasive open APIs are available, providing various data and services, e.g. gotrue for authentication, PayPal for payment, Twilio for sending SMS. API List has a curated list of many more APIs. It is incredible to imagine back ten years ago that these services can be called from frontend directly without setting your own server!

If none of the APIs meet your need, you can set up your own APIs. Microservice is a good fit in this ecosystem. You can simply create a function and expose it as a service.

Git backed CMS

While CMS (content management system) is not a necessity for JAMstack, it definitely boosts popularity of JAMstack. Considering that the majority of websites today is powered by Wordpress, more content authors are contributing to websites than technical engineers. Therefore providing good author experience is critical for JAMstack adoption.

Traditional CMS (e.g. Wordpress) saves content in database and is a typical monolithic architecture that includes database, server and frontend rendering. In contrast, JAMstack removes dependency on database and saves content in Git. Content is saved together with code, and gets version controlled as well. These modern CMS provide UI for editing content, automatically commit changes to Git, and provide Web hooks when content changes. They do not care how the content will be rendered to the end user, leaving it to the frontend app. Therefore they are also referred as “headless CMS”.

Static site generator

In JAMstack, frontend is usually a single page application and needs to manage more logics and states. UI frameworks like React make it much easier to develop complicated frontend apps. A static site generator builds upon UI framework, allowing developers to write in their preferred javascript framework but generate and optimize html during build time. While Next.js, Gatsby, and Nuxt.js are most popular, there are tons of site generators available.

Conclusion

When talking to Kyle in the conference, I was impressed by his comment: in the end of the day, it is about caching (not exactly, but similar). In monolithic structure, a new dynamic html is built with every request and thus no cache is done here. Caching is created at various other layers to help with performance, which eventually makes it more complicated and less predictable.

In JAMstack, the pre-built html is a cache sitting in CDN. A new deployment changes the file and wipes out the cache. Caching becomes a simple file system and is thus much simpler and more predictable. In that sense, a “static” app means static files ready to be cached, and easy to be invalidated with a new file.

From the perspective of developer experience, JAMstack enables separate of concern. To deliver content/service to end user, developers used to have to take care of the whole stack of operating system, database, server and UI. Tooling around JAMstack modularizes each task. Frontend developers can focus on UI development. The task of managing server/database/OS changes to simply finding and connecting to a proper API. The change of task, in turn, gives birth another tooling, GraphQL, which makes data/services more discoverable and usable. I can imagine one day all open APIs merge into one giant graph, providing developers a single source of truth to connect and consume services. In fact, people already start to work on this! Yes , we are witnessing a lot of super exciting movement in Web!