Ask HN: What are the common pitfalls/problems of Single Page App's?
I feel like a SPA style app works well with the use case of the site I have been building in that we are making it easier for our customers to find their information in the shortest amount of time possible (think Google for product support).
With that said, I have a bias towards this design choice because I built it. I know that I might be missing something and I want to consider more use cases than what I can come up with.
What I want to know is when does it make sense to use a more traditional server-client architecture? I had read about Twitter reverting back from a SPA to server rendering, but I feel like their use case was specific to their needs or to a specific metric and possibly user experience for their site (lower time to first tweet, smaller page load, and cleaner URL's).
I am aware of SEO drop with Angular, but I feel like problems like clean URL's, back button not working, and UI thrashing are fairly solveable by good planning (in the case of the URL's) and Web Workers (to address UI Thrashing).
I chose Angular because I was already very familiar with it and I found it to be fairly flexible for what I need it to do, it just needs some nudging at times (ng-if vs. ng-show/hide). Also, at the time, I couldn't get the JSX tooling for React to work properly and I think Babel couldn't do transpiling for the latest version of React at that time (I think it was around 1.4). I had used Mithril which was a huge joy and very powerful, but the way my site works (it's an Oracle product using PHP/CodeIgnitor), I couldn't get the client side routing to work properly.
20 comments
[ 3.0 ms ] story [ 60.7 ms ] threadI read that the workers can't get font measurements, which prevents layouting on webworkers.
I use it on the first page load so that I can get all of the data I need for most of the routing upfront so that I don't need to perform an AJAX request for every state change. I was originally having the server render JSON inline with HTML but Web Workers ended up being faster.
One strategy that seems to work for this project has been to load in large, JSON objects using web workers while the first page is loading. This will allow the page to load and not disrupt the view rendering.
From there, I can use the large JSON objects to build the nested routes. I will use one view and link to it but add parameters to filter out the data. Ng-if has been making the rendering way way way faster going this route.
On a slow connection, two things that slows down the entire app is AJAX calls and rendering images. That is why I have tried to pull in the large JSON objects upfront so that the initial load might take an extra half second, but the rest of the app navigation is speedy.
SEO is my biggest concern, but for now, I am thinking I can use older links and do a 301 redirect to the needed resource. The only issue that I foresee is that I cannot do a server-side render; I was thinking of just routing bots to a non-ajax site so we can still get good SEO but it's while before I will get to this part.
- Angular can be hard to debug when using some architectures. Two-way binding is pretty hard to track down. Scope inheritance is also a pain, especially with nested controllers, or when devs just stick everything to the root scope and expect it to be there when it doesn't at times. Even tools like Batarang and Inspector can be a pain to use.
- Long-running SPA's tend to accumulate unreleased memory if not managed properly. When stuff like this happens, worst case scenario is to have the user reload the page and lose state. Not taking this seriously, the browser may crash. You don't want this to happen in the middle of an operation, like say, a payment.
- State management is also a pain to work with. I often hear our devs talk about "invalid state". How can it be invalid? That's unless not all changes are accounted for. This is where the Flux architecture shines, as it isolates state in one location, away from the moving parts, and state only mutates via one mechanism - actions.
- It's huge size. One issue with Angular is that it uses too much boilerplate code, most of which is because of the structure and dependency injection mechanisms. Minifiers can only do so much to lessen the size, and not sure if Rollup can do much against these helper structures.
- Nailed it on this comment. I have been trying to figure out something that "makes sense" in terms of file structure and even code organization. So far, it hasn't been too much of a hindrance, but it I know the more code I write, the more unwieldy things will probably get.
- This is something that I have been very aware of and the way I have been handling it is limiting the number of controllers in general so I do not introduce new scopes. For the most part, all of the pages do not need to introduce new scopes and the data should be consistent across the pages anyways, so this hasn't been too much of an issue (yet).
-I haven't had this issue yet, but I haven't sufficiently tested this. From some initial testing, the memory footprint has been pretty light, but I need to do a lot more on this end once I have completed more of the app.
-State management hasn't been too tricky because there's only two states that I really care about: whether a user has logged in or if they are logged off. This part I have been addressing by loading JSON for contact information into the page to both limit the amount of AJAX requests and to enforce routing. Fortunately, this state is also managed by HTTPS Cookies so this is handled server side as well.
-I don't know how to validate or dispute this, but it looks like loading the minified scripts from CDNJS for both Angular and React were the same filesize. In any case, I have been using A LOT of what comes with Angular out of the box and have been using the UI Router library as well as the ng-Sanitize library for what I need. Haven't had too many issues with this.
Initially, I was having the server load up large JSON objects inline with the DOM so that I could avoid doing multiple AJAX calls. This worked with 2 very large JSON Objects, but as soon as I hit 3, the page would have a noticeable jank trying to evaluate these JSON Objects and render the page.
I remembered this article from Hacker News a while back (http://www.pocketjavascript.com/blog/2015/11/23/introducing-...) and started using Web Workers based on the speed of the app (it's pretty fast, check it out pokedex.org). There was a HUGE difference with having that first initial page load with the AJAX calls in the background. The only thing I would recommend is that you should make your first page load not contingent upon any AJAX calls or any loops (ng-repeat in the case of Angular). This will allow you to load in the page without having to wait for any response from the server and usually, the page will load alongside the data retrieved from the Web Workers.
And the first load issue can be circumvented with pre-rendering the first page on the server.
I tried this with React and it worked like a charm. It even went so far, that the whole page doubled for SPA and complete-server-side app, when JS was deactivated.
The client needs to use the same data or else it will throw away the whole markup and re-render it.
Fetch the data on the server before you render.
Render the (static) markup with React and the data.
Send the markup AND the (serialized) data to the client.
The client will render the markup normally before React kicks in.
The client React then needs a way to use the serialized data with the pre-rendered markup.
Redux, for example, is a way to pass down this data to every component, so it's available at render.
Normally, when writing React apps, you can simply load data async and show a place-holder while the request runs.
If you want to render on server, all the data has to be fetched before, if it's loaded async, so the server can render synchronously.
Also, get designer(s) involved in the process ASAP.