What? No. Not at all. If you're behind a subpar network, your dynamic HTML web app does not load/refresh/update at all, and your users start to get frustrated because your crappy webpage is broken and fails to even do the most basic things.
This is not the case with SPAs, and some of the most pressing problems they solve: perceived performance, resilience to faulty network connections, and overall improved UX.
Let's put it this way: with SPAs you can design your app to work even without a working network connection. That's how resilient SPAs are to networking issues. How do you pull that off with dynamic HTML?
> but for 99% of websites server-side rendering only has advantages.
No, not really. Unless you cherry-pick what goes into the 99%, even basic CRUD, form-driven pages the dynamic HTML way suffers from a multitude of drawbacks that are no longer issues in SPAs, both technical and organizational.
Currently, I’m doing a hybrid sometimes where I’ll have jquery do a call inside the page when I want one particular area to be fast.
Server side I check for htmx headers, and if so omit headers/footers are just return the body. htmx then handles the html swap and boom, you’ve just turned the page without reloading the whole page.
Simple and straightforward. I also use a plugin to preload the responses on mouseover in some cases for even more snapiness.
I find Laravel + Unpoly a great combination.
Eureka exclaimed the "full-stack" developer, who needs javascript!?
If only browsers could somehow retain the parts of a web page that did not change, like images and css and js, in some sort of local storage or cache.
If you can get the HTML within the same order of magnitude as JSON itself, then it's perhaps a compelling argument to ditch SPAs. The bandwidth delta would be minimal, and the development overhead would be much less substantial.
Note that this is how the web used to be. Small HTML markup.
We even started baking structured meaning into the HTML (Semantic Web, "Web 3.0") so that documents could be consumed like APIs. But then JSON and the platform giants put an end to p2p web payloads and rich document schemas. That was a mistake.
SPAs can be great for large teams with different skills/profiles, and large companies with a lot of money though.
But don't worry! AWS thought of this! They invented Another Cloud Thing, namely Lambda@Edge, to solve this. Now you can run a JS function for every single request that hits your CloudFront Distribution so that you can run logic in there to strip the prefix before it gets passed to your origin. You read that right! Execute code every time a request hits your proxy! But wait, doesn't executing code for every single request sound insane AND doesn't it also add latency which this was trying to remove? Yes! Isn't cloud just lovely?
Yes, designed for usecases like those handled by Cloudflare Workers, which boil down to updating data cached in edge servers without requiring global redeployments or pinging a central server. We're talking about stuff like adding timestamps to images or adding headers to HTTP responses or pre-rendering some HTML or emit CDN-aware metrics.
> Now you can run a JS function for every single request that hits your CloudFront Distribution
Not exactly. Lambda@Edge are event handlers from CDN events. You use them when they suit your needs.
> so that you can run logic in there to strip the prefix before it gets passed to your origin.
Your strawman example doesn't even feature among the dozen examples provided by AWS regarding how to use Lambda@Edge.
Everyone is free to come up with silly ideas and absurd examples, but if you design systems around braindead ideas then that says a lot about you and nothing about the tools you chose to abuse
> You read that right! Execute code every time a request hits your proxy!
Yes, that's what web servers do. What exactly is your point?
> But wait, doesn't executing code for every single request sound insane
It doesn't. That's what a web server does. Moreso, Lambda@Edge (and Cloudflare Workers too) only do it if you explicitly decide to make them do it, to match precisely what you tell them to do.
What point are you trying to make, exactly?
> AND doesn't it also add latency which this was trying to remove?
It does. It adds tens of milliseconds when the alternatives can add hundreds of milliseconds. You're also expected to do basic engineering work and do basic performance work when seeking performance improvements, such as measuring things instead of mindlessly jumping on bandwagons without caring for the outcome.
> Isn't cloud just lovely?
I feel your comment manifests too much cinicism to cover too much ignorance on a topic you are not familiar nor understand the basic premise.
> Yes, designed for usecases like those handled by Cloudflare Workers, which boil down to updating data cached in edge servers without requiring global redeployments or pinging a central server. We're talking about stuff like adding timestamps to images or adding headers to HTTP responses or pre-rendering some HTML or emit CDN-aware metrics.
> Not exactly. Lambda@Edge are event handlers from CDN events. You use them when they suit your needs
> Your strawman example doesn't even feature among the dozen examples provided by AWS regarding how to use Lambda@Edge.
For all of these counterpoints, see [0].
> Everyone is free to come up with silly ideas and absurd examples, but if you design systems around braindead ideas then that says a lot about you and nothing about the tools you chose to abuse
I take it you are directing this particular comment at AWS considering they are recommending this solution? [0]
> Yes, that's what web servers do. What exactly is your point?
Don't be daft. You know what I mean. Obviously web servers execute code. In this case the web server (CloudFront Distribution) is passing the request to yet another "thing" which happens to be a JS function that is invoked specifically to handle something web servers like nginx were built to do extremely efficiently on their own. CloudFront could easily support this without additional dependencies that add complexity to your system design and introduce additional failure modes. In this case, I am making your point for you: using Lambda@Edge for this IS ridiculous, but that's the AWS way.
> It doesn't. That's what a web server does. Moreso, Lambda@Edge (and Cloudflare Workers too) only do it if you explicitly decide to make them do it, to match precisely what you tell them to do.
> It does. It adds tens of milliseconds when the alternatives can add hundreds of milliseconds. You're also expected to do basic engineering work and do basic performance work when seeking performance improvements, such as measuring things instead of mindlessly jumping on bandwagons without caring for the outcome.
Sorry for not writing a detailed blog post to describe all of my findings. FYI, the latency added with Lambda@Edge caused my request time to double and added much more variance.
I'm surprised a lot of your counterpoints are just blaming me for doing the wrong thing, when this is actually what is recommended by AWS. Invoking a custom function to process the request. See [0]. Of course the right solution is to use nginx (but then you lose out on AWS scaling) or completely redesign your system to fit another solution like API Gateway.
From AWS's blog:
> In this scenario we can use Lambda@Edge to change the path pattern before forwarding a request to the origin and thus removing the context. For details on see this detailed re:Invent session. [0]
[0] https://aws.amazon.com/blogs/architecture/serving-content-us...
I don't, I can just teach my backend to process /api. The backend is running code under my control anyway. This seems like one of the least difficult "problems" to solve in any non-trivial codebase.
> doesn't executing code for every single request sound insane
How would you strip some url path without executing code for every single request? It could be code you did not write, but some code is always needed to do something. If latency of lamdba@edge is an issue, you can try CloudFront functions, which should be better for small tasks like this (though I don't have any experience with them as we switched to a similar competitor)
Only Lambda@Edge can help the scenario which I provided, which is also AWS's recommended solution. [1]
[0] https://docs.aws.amazon.com/AmazonCloudFront/latest/Develope...
[1] https://aws.amazon.com/blogs/architecture/serving-content-us...
but surely executing code at the source of your web app is more efficient than having the client and server attempt to perform it? faster than 40-90ms per request at least
No, CloudFront Functions is the Another Cloud Thing AWS invented more specifically for this use case. Lambda@Edge is the older and more general purpose edge computing facility.
> You read that right! Execute code every time a request hits your proxy!
Any rewrite rule in any platform, and heck, any other functionality provided by the distribution, is executing code every time it gets a request (whether or not you are writing code for the purpose.)
Only Lambda@Edge can help the scenario which I provided, which is also AWS's recommended solution. [1]
[0] https://docs.aws.amazon.com/AmazonCloudFront/latest/Develope...
[1] https://aws.amazon.com/blogs/architecture/serving-content-us...
Only Lambda@Edge can help the scenario which I provided, which is also AWS's recommended solution. [1]
[0] https://docs.aws.amazon.com/AmazonCloudFront/latest/Develope...
[1] https://aws.amazon.com/blogs/architecture/serving-content-us...
Request manipulation is not the duty of a cache - even though other CDN providers mix request manipulation functionality with caching. In my opinion, they don’t need to be in the same product.
If you still need request manipulation, because you don’t control the origin or you don’t want to introduce another service between CloudFront and the origin, you would use CloudFront Functions, which is cheaper than Lambda@Edge and easy to set up.
I agree that AWS products are all over the place.
It's the customers. They run into issues using something against all advice in the docs and then request features and services or AWS sees all the clamor around a perceived issue and they create a "solution."
Developers are really averse to reading and understanding documentation. It's why they all jumped on the cloud. But it turns out it isn't all just magical infinite performance.
Is your lambda awake?
app.UsePathBase("/api");
And we have 0 issues.
I have hope that we can remove it, though, in new versions of HTTP.
CORS only exists because XMLHTTPRequest broke the assumptions of web 1.0 servers. Suddenly any web browser loading any page anywhere could make a request to your server without the user's explicit permission, and a ton of web servers had already been built and were running under the assumption that users would only hit their endpoints by explicitly typing in a URL or clicking on a link.
But each time we design a new version of HTTP, we get an opportunity to remove CORS restrictions for it, because there aren't any servers running the new version yet.
And with Braid (https://braid.org), we're making changes that are big enough that I think it really warrants taking a fresh look at all of this stuff. So I have hope that we can eliminate CORS and all this ugliness.
This had always been true; making cross-origin requests with e.g. <img> tags is still very much a thing.
Apparently, since developers should already have been protecting against cross-origin requests on GET and POST via other means (CSRF tokens), there was no need for the additional preflight protection.
They only added preflights to requests that browsers couldn't make previously.
Thats the threat model CORS is meant to address. Not just in general that a web host might not want a request from "anywhere" happening, but that site B might not want an authenticated request on behalf of User X to site B being made by code from site A.
1) POST.
2) Of arbitrary content.
3) With an arbitrary Content-Type header.
You don't get an OPTIONS for the sort of XHR request that you could do with an <img> tag (always GET). You don't get an OPTIONS for the sort of XHR request that you could do with a <form> tag (GET or POST). You only get OPTIONS if one of the following is true:
* Your request method is not GET/HEAD/POST
* You set a header value for a header other than Accept, Accept-Language, Content-Language, or Content-Type.
* You set Content-Type to a value other than "application/x-www-form-urlencoded", "multipart/form-data", or "text/plain".
* You have upload listeners on the XHR upload.
* You use ReadableStream in the request.
Like robots.txt for the search engines.
If you control domain.com and api.domain.com, then you can create a proxy that glues the two to the same domain, getting rid of any CORS annoyances forever. And you use the tech that exists. The whole problem takes less than 1 minute to type, test, deploy and there's no need for yet another big thing invented to solve a small problem that occurs due to ignorance of self-proclaimed "developers".
But it's also necessary.
It's difficult enough to implement that it probably won't be implemented on the majority of the web - and maybe that's okay.
This is what CORS is though.
Using Emscripten and threads, you require COOP & COEP headers to be sent via the main document. This is not common practice for static html hosting sites, thus requiring that you have access to a config file or .htaccess, and requires ALL assets to be hosted exclusively on that same server.
Killing my project is a bit extreme, but killed my motivation.
It's a web-based game with multiplayer that I was hoping people would modify and expand on, hosting themselves, uploading to places like Github.
I've recently started modifying Emscripten's runtime library to try and treat each webworker as a separate instance that just communicates between each other. This has major overhead as each thread is a new memory instance. I've tried getting it to extract each function into it's own module for a webworker to load but that's a major task.
Web apps want to act as desktop applications but they're so held back by security it's nearly impossible. We don't even have a proper local storage system.
To quote Dilbert, "Security is more important than usability. In a perfect world, no one would be able to use anything."
What static file hosting were you using? If it doesn’t support COOP/COEP headers yet, you can force it by using a service worker with this neat hack: https://stefnotch.github.io/web/COOP%20and%20COEP%20Service%...
Firebase hosting would allow you to set headers with a .htaccess file. Cloudflare Workers can also set the headers.
If your project was going to entail loading resources from other origins not under your control, well, that’s exactly the code injection scenario these headers are designed to prevent.
I know that Netlify does[0], and use it myself, so I would expect the other big names to support something as well.
It didn't out right kill the project, It'll work under these specific conditions but reducing usability is not helpful.
I don't like some of the candor of the article. Treating cross-origin activity as a mis-feature is a great mis-service to the web: it's not really much of a web, imo, if sites are restricted to talking to themselves. That the web standards crew is happy to shit on this range of capabilities, to write new standards that walk the web back: it's a harsh regression, by afraid leaders. There is a lot a lot a lot of trouble here, in these domains, but this desire to just cancel the feature & walk away, to make the web one where sites have to work & play only with themselves: it's a death knell, against the spirit of the thing, deeply. That it's so inconvenient & troublesome to standards authors, so difficult for browser engineers, that it (used to be) a hazard for sitemasters: I'm sorry for your suffering but good things & great powers have a price. You're wrong to want to shut this possibility out. Easier path that it may be.
Is it to point requests to sub path of the main domain to reduce OPTIONS requests? eg: api.example.com ---> example.com/api
In that case why not use proper "Access-Control-Max-Age" headers?
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ac...
Another thing to consider is avoid hitting your api application with those requests.
You quite probably don't need authorization or any other business logic in that preflight. You can just catch any OPTION or OPTION+preflight headers in your proxy, webserver or balancer and handle it there.
You certainly don't want to handle them in Rails/Rack, nodejs, lambda, django, spring or such.
This makes them so much faster for users, and so much lighter for servers that the once per 2 hours cached request hardly is measurable, even.
Even with that header users will still hit that latency on the first request. If the goal is to lower latency on first page load then that won’t help. Although it should definitely help after that.
<link rel=preconnect crossorigin=anonymous href="https://api.example.com">
or
<link rel=preload crossorigin=anonymous href="https://api.example.com/emptyendpoint">
just to initiate the connection/CORS dance early one before your JS kicks in. In my prev job we'd do a request to empty endpoint from inlined JS. Poor hack but it worked.
You can answer the options request at the webserver level or even Varnish which should be quick enough and the http connection reused.
Anyway, today we have the tools to expose different backends through the same domain. Reverse proxies, k8s ingress, … a separate domain may not add much value in most situations.
If your users make only one request per two hours, that still doubles the requests, so in that case caching'll hardly help. But in more typical cases, where one session does 20+ requests, it's a meagre 5% of the requests. In which case 'resolving that n+1 request' or 'speeding up that 1200ms response' is far better low hanging fruit than removing the single OPTIONS request.
It depends on your case, though.
The resulting architecture does not require any CORS requests, too:
https://manuel.kiessling.net/2021/05/02/tutorial-react-singl...
I think it's time we take a step back and address this insane complexity required to send HTML documents to users...
Instead, we now have a reverse proxy (haproxy) that "fixes" the missing CORS headers, by intercepting the OPTIONS call and return a dummy response with the correct headers included. The developer basically understand NOTHING in regards to CORS, so whenever the silly SPA breaks, the logic is always the same: "CORS is broken, fix it". At not point has it been an option to fix the API service to include the correct headers.
We could just have moved the API to /api and saved days of debugging and writing work-arounds, but no, api.customersite.com looks more professional.
It’s not that complicated but when something breaks CORS adjacent, you’re stuck reviewing the gotchas.
I inherited both front and back-end. Their solution was "allow: *" (which is like saying, "I don't like carrying my house keys, so I removed the lock")
The system did require a "api.*" for other things, so my quick solution was a proxy_pass for /api.
...tap ...tap ...tap
No. Second top answer. https://stackoverflow.com/a/27280939/1507124
With the obvious WTF top comment and the natural follow-up. It solved my problem, therefore it's great.
See for instance this comment: https://news.ycombinator.com/item?id=29778528 Which suggests implementing the CORS response in a reverse proxy or some other covering layer.
And of course the OP also involves a reverse-proxy, although one mapping to avoid the need for CORS headers.
Browsers prevent most types of requests that go from one hostname/port/protocol to another by default. CORS is a way for a server to tell browsers to relax these restrictions in some way.
If you disable CORS, all that means is that the default browser behaviour applies, which means that more types of requests are prevented.
Then of course you don’t need CORS
For most if not all use cases, you can set up a proxy in your front-end's web server so that it doesn't need CORS.
I would use a mix of your solution, i.e. allow the Java script client to be configured with either absolute or relative urls in case one needs to point the client elsewhere + reverse proxy
I'm guessing not, but if there was, theoretically could you just include your API response in the CORS rejection response?
[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OP...
(You can even do POSTs without preflight if you use a whitelisted content-type.)
And the case where preflight isn’t needed despite using CORS.
I remember taking a look at this a while ago and only found ways to do this through workers, so was turned off by the potential cost scaling. Ended up just setting a high `Access-Control-Max-Age` and calling it a day. But maybe I've missed a more cost effective way to accomplish this?
[1] https://support.cloudflare.com/hc/en-us/articles/206652947-U...
[2] https://community.cloudflare.com/t/rewrite-host-header-in-pa...
Context here: I'm maintaining a PWA that has to run on local iphones/android devices and maintain contact with a server on local networks in the 127.x block. But the point of download for the app itself is under an https domain on the open internet. It uses an iframe to check lots of local 127.x.x addresses until it finds one with a local server, and bootstraps itself to the code on the local server that way; unfortunately, it can't run as a true PWA because the iframe at the center of it violates CORS (due to the ban on mixing clear and SSL requests in the more recent versions of Chrome and Safari). Would be nice if the local servers could simply serve up their content and control the window without a whole domain-specific postMessage protocol.
You can’t do that because in your case only the client can connect to the local networks; your backend has no access.
Try the API playground[1] on the authors site. Its takes more than 1 secs for me to get a preflight response back.
The preflight request hits fastly and aws apigateway and maybe the application well. There are lot of options to solve the problem at fastly and apigateway as I mention in another comment[2].
I really hope author reads the comments here.
On a side note at previous company I've worked at, speed was critical, so we were forced to do same tactic, use /api instead of api.domain.com which resulted in huge improvements :)
I have good news: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ac...
[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simpl...
…if you’re using a proxy like Cloudflare.
The short version of this post is that if your api is hidden behind Cloudflare, you can have it proxy requests to you api that lives on a subdomain.
- Set the Content-Type to "text/plain; charset=dropbox-cors-hack" instead of "application/json" or "application/octet-stream". [0]
...which is allowed in a "Simple" request that doesn't require a separate round trip for OPTIONS.
[0] - https://www.dropbox.com/developers/documentation/http/docume...
* `Sec-Fetch-Site: cross-site` can be `Sec-Fetch-Mode: no-cors`
* `Sec-Fetch-Site: same-origin` can be `Sec-Fetch-Mode: cors`
It looks like all four combinations of these two headers are possible.