Hacker News new | past | comments | ask | show | jobs | submit login
Immutable Web Apps (immutablewebapps.org)
248 points by octosphere on Dec 5, 2018 | hide | past | favorite | 62 comments



I wish there was some "why"

> Static assets must be hosted at locations that are unique and independent of the web application environment(s)

Why? Meaning a different domain or is path enough? What are "static assets" compared to every other file served by a web server? I assume meaning will never ever change, so images are not "static assets"? What if I name them by what they are and commit to changing the name when I change the image?

> Static assets are the files (javascript, css, images) that are generated from a build of a web application codebase. They become immutable when they do not contain anything environment-specific

> Static assets must not contain anything that is environment-specific

So, static assets are only immutable when they don't have anything environment specific. Oh, and static assets always don't have anything environment specific. I'm confused in the wording. Clarification required. So my build generates some environment-specific CSS file and in one paragraph that's a static asset and in another it's not?


> Static assets must be hosted at locations that are unique and independent of the web application environment(s)

> Why?

Broadly, for the same reason that separating content, structure, and style is considered a good idea. (We can debate separately about whether HTML/CSS/JS applied this principle appropriately.)

From another angle, this is just another way of saying that static assets should not depend directly on the environment, and that only `index.html` should differ, since it's the effective manifest of your application. It's okay for different environments to go to a different URL for `index.html`.

> I assume meaning will never ever change, so images are not "static assets"?

An individual image is a static asset. If you want to use a different image, you should change the URL. This is where the "immutable" part comes in.

> So, static assets are only immutable when they don't have anything environment specific. Oh, and static assets always don't have anything environment specific.

Not every static asset is a build artifact, but every build artifact should be a static asset. This is not true in general, but it is what the whole idea of an "immutable application" is trying to achieve. The environmental bindings are pushed out to the top level, where they belong.

I agree it could have been worded better; the phrase "static asset" is a bit overloaded here.


Can you clarify, if I add a JS line to my file, I should version that JS file as a never-ever-changing static asset to be cached? It is a build artifact. And if I make a change daily (or more frequently w/ CD), have 365 of those versions (i.e. maybe versioned as date or hash)? Or should I start embedding that frequently changing JS in the pages that need it, thereby duplicating content, just to avoid breaking the rule that all build artifacts are static assets and all static assets are immutable?


> Can you clarify, if I add a JS line to my file, I should version that JS file as a never-ever-changing static asset to be cached?

It only has to be cached as long as there exist deployments that need to reference it. But as long as there exist references to it, the content at the referenced address should not change. So yes, you should version that file -- or that deployment as a whole, if you'd rather not get that fine-grained -- but it only has to be "never-ever-changing" up to the point that nobody's observing it anymore.

If you only officially maintain one version, you'd want to keep the one prior for the sake of smooth deployment. Just document your support policy so downstream consumers aren't surprised (and they should be able to pin to an `index.html` address, generally, whose contents _are_ allowed to change over time.)

> Or should I start embedding that frequently changing JS in the pages that need it, thereby duplicating content, just to avoid breaking the rule that all build artifacts are static assets and all static assets are immutable?

Well, the article is about SPAs, so there's only one page by fiat. But in the general case (MPAs?), there would be a question about why that specific piece of JS must change so frequently, and whether that constitutes a dependency on the environment that can therefore be factored into the top-level.


I understand the general note about caches not lasting forever, no longer referenced files, etc.

This response skipped over the most important question "And if I make a change daily (or more frequently w/ CD), have 365 of those versions (i.e. maybe versioned as date or hash)?". Simply asked, is every generated JS file a static asset? Must the (indefinite) cache headers be present for every static asset be returned? So if I deploy a new static asset every hour via continuous deployment let's say, I must give it a new unique path? These questions apply to SPAs.


>So if I deploy a new static asset every hour via continuous deployment let's say, I must give it a new unique path?

I would answer yes to that. More specifically, if an old cached component could reference the new file and break, then the old component should never know about the new content.


> And if I make a change daily (or more frequently w/ CD), have 365 of those versions (i.e. maybe versioned as date or hash)?

It's a good practice for any web application. You place huge caching times for your assets, and make their URLs unique.

At the backend, you either have a compilation step that adds the unique marker (a hash is more common) to your links or you have the backend add it at runtime.


If I were to build an application 100% in the style of the article, my answer to all three questions would be "yes". But I'd probably opt to version at the level of a whole deployment, rather than at the level of each file. There's a case for versioning non-build static assets separately, but I'd rather version all of the output of a build process together.


Unique file names also solve poorly behaved caches. I spent too long at a previous job trying to figure out why a particular user kept hitting the same error even after it was fixed: it turned out to be his computer's clock being off significantly enough that assets that should have expired from his cache were not expiring.


well actually when you host an SPA and you DO use lazy loading (which you probably need to do on angular, because of sizing, etc...) than you will run into problem if you don't put all assets into a second webserver that will hold all assets (i.e. all versions). if you try to deploy these assets directly with your normal application than you will probably run into serious trouble when users that do not have the newest version will try to download these lazy loaded stuff.


> well actually when you host an SPA and you DO use lazy loading

This is not always true. Many times frameworks are so heavily leveraged and the site-specific code so minimal on small SPAs that it is reasonable to have a single packed-and-minimized JS file. And it still may change frequently.


yeah as said, only when you do, but on angular you probably need to do. however I had good luck with vuejs which basically was relativly small. I also tend to do lazy loading pdfjs or something really big.


> So, static assets are only immutable when they don't have anything environment specific. Oh, and static assets always don't have anything environment specific.

If I understand correctly, the immutability of a particular asset is what makes it a "static" asset.


Ah, that would make more sense. So not generated mutable files aren't necessarily static assets? The wording is a bit confusing, because when one reads "Static assets are the files [...] that are generated from a build of a web application codebase" they rightfully assume "the files that are generated" means "all generated files".


One thing I liked to do was separate a "build" environment from a "runtime" environment. Build envs would be the standard debug and release, runtime would be dev/staging/production like usual. The runtime environment would be in window like shown here and the build environment would be baked in. After all, it doesn't make sense to flip the DEBUG variable for example, but it is very convenient to be able to change what backend you're connecting to to test something.

Also: other than index.html, it's worth putting hashes in every filename. Though you will surely run into problems when the app is updated but users are running old versions so make sure you build with that in mind. I.E. you want to be able to reload at any time without consequence, and you probably want to do it automatically when you can't find a chunk. That last part is tricky if you map all 404s to index.html :)


> All of the leading application frameworks (Angular CLI, Create React App, Ember CLI, Vue CLI 3) recommend defining environment values at compile time. This practice requires that the static assets are generated for each environment and regenerated for any change to an environment.

As a backend developer that relies heavily on environment variables for configuration, I was surprised that runtime configuration is not something these frameworks recommend.

In the few occasions that I have to do some front end (Angular) stuff, I had to write my own service just so that I can have runtime configuration.

Why don't these frameworks support this out of the box? Are there any issues I may be missing?


You can definitely do runtime based configuration. The bigger advantage to environment configuration generation at release is that you can target strictly static deployments. No need to support anything but HEAD/GET requests on said server, which can minimize a lot of risk in the environment.

The application I'm currently working on has a separate configuration project, more for application options for one client deployment vs. another at a feature level, as well as any customized localization/strings. Beyond this, there is are release variables generated that are also injected. Namely the endpoints for login/logout/user-management/api. The application itself doesn't handle authentication, it validates a token/key with the api endpoint. Other configuration options are loaded from the api endpoint on application startup.

The application itself will be loaded with the strings for the application and a small spinner and load-check. If the browser doesn't have JS or meet minimum requirements (ES2017 async function support), it will load another screen acknowledging as much. It will then verify the authentication token, load other parameters from the API/Database and proceed to client-side application routing/runtime.

It isn't particularly difficult to setup. Initially I had a complimentary server-side that would deliver the environment options at runtime. That was changed in order to facilitate static deployments of the application.


You can't have runtime configuration on the frontend because the frontend is running on the client (browser). The best you can get is build-time configuration that embeds in the SPA.


Here's an example where it can be done in Angular: https://www.jvandemo.com/how-to-use-environment-variables-to....

Not exactly the same as shell environment variables, of course. And you may disagree about exposing the `enableDebug` flag as a runtime config there.

Still it is doable, in a sort of roundabout way, using the browser as the environment.


Ah, indeed. If you count "open dev shell, manually edit globally exposed variables so that it will affect only my browser" then yes, you can have that as runtime configuration.


Yeah funny


I come from a time where compiling your code, even a small one, was processor time consuming.

Back in University, in the beginning of the 90’s, a teacher explained us that separating configuration from code was a good practice because you don’t have to recompile your code to make several tests.

I guess it still is the case !


I think it comes down to:

Make index.html the starting point, that pulls in the immutable web app, and configures it, (usually with a single endpoint URL that it would typically need).

Seams reasonable to me. I'm normally pessimistic about things like this (specifically the 12 factor app which is hopelessly one-sided to specific environments IMHO). But this is actually a useful and captured quite gracefully.

It would be nice to see some examples in each of the mentioned frameworks, and hopefully example webpack configuration specifically (since it is often used across all of them).


Immutability (and the related idea of forming things in a declarative instead of procedural fashion) is a good idea generally and prevents vast swaths of possible bugs from ever occurring.

SPA's are a bad idea because 1) it greatly complicates good unit testing and requires gross things like headless browser drivers in your integration test suite, 2) it does not degrade gracefully to HTML (such as on assisted devices) 3) javascript is, at best these days, a sunny wasteland of dependency hell.


1) Writing unit tests for a SPA doesn't have to be much more complicated than unit testing a server-side application. Integration testing is a bit hairy, but that's generally the case in many technology stacks.

2) SPAs don't have to be pure HTML to be compatible with assistive technology.


We do something similar to this but with server-side rendering. We build our apps with React, then we use React Router to match routes on the server and send down CSS and HTML as needed by the current route. Client-side environment variables are put on the window object by the rendered HTML, just like in this article. Then, once JavaScript kicks in, there’s no configuration “bundled” into it. The configuration is just parsed from the server’s HTML. The JavaScript bundle lives in a CDN and follows their definition of “static”.

This is basically 12-factor [1] though. For us, this was imperative to be able to do review apps on every merge request and promote those to production without having to re-run the build. We run the exact same Docker image that ran in CI, the environment is the only thing that changes.

[1] https://12factor.net


Suggestion: encourage the usage of SRI hashes when linking to these externally hosted assets.

https://www.srihash.org/

These seem to neatly complement the other aspects of this proposed architectural pattern.


This is essentially an opinionated take on how to deploy JAMStack style applications. TLDR is to use index.html as deployment config / manifest ONLY, and load all other assets from static unchanging resources. This kind of discipline is a little like what you might use for an optimal IPFS (content-addressed) deployment, so it's cool to see best practices converge.

I clicked through and read the article because I though it would be about Clojure-style immutable data structures, like ImmutableJS uses. Or maybe how to use databases like Datomic or patterns like CQRS and event sourcing.


I'm surprised there's no mention of Subresource Integrity. That works well with content addressable stores, provides extra verification inside of the browser.


Interesting, I've never heard term JAMStack before. I knew this was a common concept to have pre-built templates with JS client and API backend, but now I know it has a name!


Stop trying to make JAMStack happen /Mean Girls


Isn't this incompatible with server side rendering? Rendering the page's contents initially on the server is good for performance, SEO, accessibility, etc.


Since this page is talking about SPAs, the only "entry point" it considers is `index.html`. I don't see anything against pre-rendering parts of `index.html` on the server-side -- just that the configuration embedded in `index.html` of _which_ static assets to use and _what_ the environment defines must be managed.

In multi-page applications (MPAs?), among the other things that would probably have to be accounted for, you could treat all of your pages as "entry points" in the same way as `index.html`.


Nope - works perfectly. With React/Redux you can inject the initial state into the rendered page. Subsequent calls are then just api.

See https://redux.js.org/recipes/serverrendering#inject-initial-...

for ideas.


There are elements of the article that argue against that though. It argues that the index.html will be small (and thus not an issue to not cache), which will not be the case if you're server-rendering pages.


Interestingly, this is shocking close to something we've been building for our internal dashboards. The only slight variation is that we serve the URLs of the JS from one of our backend services over CORS - because we want access to the code for the dash to be subject to authentication, so all our fraud controls etc aren't super easily visible.

So if there's no valid auth, it goes to our `service-dashboard-asset` system and requests a URL for the login app - injecting the script tags into the page, then the user can authenticate.

Then the page refreshes, the auth is present, and it goes to `service-dashboard-asset` and requests a URL for the actual dashboard, and now injects this script tag in instead.


> The methodology is based on the principles of strictly separating:

> - Configuration from code.

> - Release tasks from build tasks.

I've built an open source product[1] that tries to address these same issues (and others). Configuration-at-build-time has especially been a pain point for my company, leading to Jenkinfile-s full of environment variables. I've also solved it by injecting a global (window.APP_CONFIG in my case) into the served index.html. It would be nice if a standard could emerge for this (I could definitely see adding support for window.env to my product).

I really like the idea of using absolute URIs for assets. Having relative URIs does indeed complicate routing quite a bit, and in fact to avoid having to specify the main URL of the application at build-time I had to implement some redirect magic, which you wouldn't have when porting the app to a "normal" static server like nginx or S3. I didn't find in the docs though, how would it work for resources linking other resources? For example, a css file loading an image via @url(...), or a js dynamically constructing the src of an img element. In the css case I guess you could hardwire the static assets URL at build time. The js case would be more tricky, but maybe it's just a practice to avoid.

[1] https://staticdeploy.io


This has been a very interesting read. I've been wanting to build a single page personal website as a means of learning. I'm not a star in webdev so I have a lot of hours ahead of me.

I might actually deploy it like this, why not?

If anyone has any good book or other resources to get started in building a website like this for people who already have back-end and programming experience but lack html, css, js etc.. please reply them to me! Thank you


I have found MDN [1] to be an exquisite resource for web technologies. I only use it as a reference since I'm already familiar with HTML/CSS/JS, but I would imagine their learning documentation to be high quality as well.

[1] https://developer.mozilla.org/en-US/


I'd recommend checking out Elm: https://elm-lang.org

it's ridiculously good and if you don't have js experience that shouldn't get in your way. it's easily my favorite frontend language.


I really like Vue.js for SPAs. I come from a Node/Php/Go back-end. Vue was the second front-end framework/library I learned, the first being Angular 1. Vue is way more intuitive. If you have experience with JS its really easy to get rolling. If you have no JS experience, just remember typing is a myth and you'll be fine ;). They also have a few examples on their website.


This article is great in explaining why modern javascript development is so complicated, with so many tools (webpack, babel, ...), and why it all makes sense:

https://medium.com/the-node-js-collection/modern-javascript-...


The proposed solution in "index.html contains fully-qualified references to the static assets" seems backwards. Changing resources from `./main.js` to `https://assets.myapp.com/apps/1.0.2/main.js` makes the opposite of an "immutable application" in that loading the script will effectively be a side-effect and depending on other factors than just the index.html link with it's child directories.

Overall it seems like a good framework, but not making all resources self-contained + relative, seems like a step backwards in that you're locking your resources to a specific domain, which might or might not be available.


It's immutable in the sense that `<script src="https://assets.myapp.com/apps/1.0.2/main.js">` will always load the same resource whenever the page is loaded (especially if it's cached indefinitely).

Where as `<script src="./main.js">` could potentially load different versions of `main.js` depending on the release version.


And particularly, if you always host the old versions then you can rollback by updating index.html - rollbacks are otherwise hard when you start thinking about caches.


That was an educational read, and gave me a good idea of an efficient strategy for deploying static web sites/apps.

I wonder how this could work with server-side rendered apps or pages?

EDIT: Aha, since index.html (only) is dynamically generated and can be populated with pre-rendered state and content.


I would separate app config into ./config.json and then load it before rendering instead of putting everything in ./index.html.

SPA bundler would generate all files as usual. index.html would be versioned. Configs would not be versioned (except for config examples).


The example on this page is really bad. Remember the time of framesets? We wrote fallback code, at least a little excuse, to all the browsers and users who did not understand framesets. There are too many "single page applications" (why don't call them "Javascript applications") which just show white space without Javascript enabled (not even to speak about ill-functional Javascript).


While I agree with you that not showing a clear message if JS is disabled on the client is bad, I would say that's outside the scope of this manifest. This is about building and deploying SPAs, not about graceful degradation etc.


A couple of weeks ago, I demonstrated a blog configuration for a client that was both immutable and serverless -- but you could change the design and add/modify/delete blog entries, of course.

I did a video showing off the web-app side. I'm probably going to do another one showing off the server-side stuff.

It was fun to do. The various pieces of tech are all now coming together nicely.


I'd watch those videos if you care to share them


Here you go. https://www.youtube.com/watch?v=XUjfD55SMxc

There's an AWS piece not mentioned here. Basically there's a way to hook GitHub and AWS together such that when you check items in, your website auto-updates using Amazon's SW and CDN tech. (I didn't cover that because it was outside the scope the question I was answering.)


Shouldn't this be called "Immutable Resources" rather than immutable web apps? Sure with JS the resources make up the app, but that index.html is still mutable, so no much different from an "index.php?section=whatever".

Setting such long expiry times on the cache also sounds like a great way to pollute browser caches with obsolete resources.


"Setting such long expiry times on the cache also sounds like a great way to pollute browser caches with obsolete resources. "

Setting "forever" caching is standard practice for web development for many years. The browser doesn't really cache these forever - it is merely a hint to the browser that the resource won't change and you don't have to check it again.

In fact, the more resources you can put with such caching the better both for you and the browser's speed.


You really can't control how long something stays in a browser cache. The user can clear it at any time. Mine are cleared every time I close the browser.


Like I said the header isn't to control how long something stays in the cache. It's to control how often the browser needs to check if the content changed.


Browsers have to worry about cache pollution already, I'm sure existing heuristics are more than capable of handling this use case.

I agree with your first complaint however. This seems like an over-complicated way of assuring that browser caches don't bite you.


It's pretty much a free option for most environments for modern web applications. Webpack can be configured to drive a version or hash based output easily.


Yes the name is too encompassing. Almost click-baity for what it is.


In a world of continuous deployment, one persons configuration is another persons code.


Well that's the issue with constants in general. What is constant in your problem is a variable for the problem I solve...


yep, more or less how approach it.

1. build js,css,img assets

2. upload assets to CDN under unique urls (at build time, for any build that may or may not produce a deployable container)

3. build docker container with server that serves dynamic html referencing those assets

4. deploy said container

Main advantages for me:

1. easy to rollback

2. graceful rollover, all assets any deployed container can reference will always exist, even if multiple versions are deployed at the same time during deployment

3. immutable === cacheable forever

4. CDN always "warm"




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: