
Immutable Web Apps - octosphere
https://immutablewebapps.org/
======
kodablah
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?

~~~
Twisol
> 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.

~~~
kodablah
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?

~~~
Twisol
> 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.

~~~
kodablah
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.

~~~
pixl97
>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.

------
jchw
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 :)

------
bow_
> 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?

~~~
alexandernst
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.

~~~
bow_
Here's an example where it can be done in Angular:
[https://www.jvandemo.com/how-to-use-environment-variables-
to...](https://www.jvandemo.com/how-to-use-environment-variables-to-configure-
your-angular-application-without-a-rebuild/).

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.

~~~
alexandernst
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.

~~~
ttty
Yeah funny

------
JeanMarcS
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 !

------
rhacker
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).

------
pmarreck
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.

~~~
jamesgeck0
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.

------
migueloller
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](https://12factor.net)

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

[https://www.srihash.org/](https://www.srihash.org/)

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

------
jchrisa
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.

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

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

~~~
__oh_es
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-...](https://redux.js.org/recipes/serverrendering#inject-initial-
component-html-and-state)

for ideas.

~~~
jugglebird
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.

------
BillinghamJ
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.

------
pscanf
> 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](https://staticdeploy.io)

------
apexalpha
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

~~~
majewsky
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/](https://developer.mozilla.org/en-
US/)

------
diggan
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`](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.

~~~
knubie
It's immutable in the sense that `<script
src="[https://assets.myapp.com/apps/1.0.2/main.js">`](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.

~~~
nmca
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.

------
lioeters
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.

------
marius_k
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).

------
ktpsns
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).

~~~
mtarnovan
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.

------
DanielBMarkham
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.

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

~~~
DanielBMarkham
Here you go.
[https://www.youtube.com/watch?v=XUjfD55SMxc](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.)

------
jarfil
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.

~~~
Illniyar
"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.

~~~
ams6110
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.

~~~
Illniyar
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.

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

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

------
janpot
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"

