React/Redux isomorphic / server-side rendering and media queries
R

2

7

I started creating an isomorphic React/Redux app based on Node. One requirement of the project is "adapative" rendering of specific components based on a "mobile" and "desktop" view. I have implemented Redux action and reducer to store screen-information about the user's view (based on media queries - "small", "medium", "large") in the state. On resize the state/store gets updated. The default state is "small".

const defaultState = {
    isMobile: true,
    isTablet: false,
    isDesktop: false,
    sizes: {
        small: true,
        medium: false,
        large: false,
        huge: false,
    },
};

In the component which needs to be rendered "adaptive" in two different versions based on the screen size, I simply do a:

if (small) return variation1

if (medium) return variation2

All working.

Now I am facing two problems:

  1. my app is isomorphic, that means the markup renders also server-side. The server doesn't know anything about the user's browser and media queries. So because my default state is "small", the server will always render "variation1". The node-server is the entry point for the site. It looks like the rendering needs to be "delayed" (middleware?) and the server needs to get some information back from the client about the browser width before the React app gets "delivered". Any idea how this problem can be solved?

  2. because the rendering is based on the state, after load "variation 1" can be always seen first for a few milliseconds (flicker), even when the browser size is "desktop". This is because the JS detection takes a few milliseconds before the state gets updated with the current screen width. I think this plays together with above problem and the default state.

I couldn't find any solution for 1, but I guess there must be something isomorphic AND responsive/adaptive.

Roomette answered 7/6, 2016 at 12:12 Comment(2)
I'm curious about what "variation1" is. Not certain yet if it's relevant to your question or not, but is the variation just an alternative rendering of the view, or is there something else happening as well?Wolbrom
"Variation 1" and "Variation 2" share same data, but the view looks different and also acts different. Like 1 is a accordion-kind-of-thing and 2 just a static layout. Not relevant to the question I think as well. The problem described above is more general.Roomette
D
0

You can get user agent from header on server, then dispatch an redux action with user agent information with a reducer that return the data, this way you can detect whether user on mobile phone/tablet/desktop/etc and render accordingly

Delectation answered 7/6, 2016 at 12:41 Comment(3)
I honestly feel that just detecting device is insufficient these days. You need to detect resolution as devices themselves have such varying resolutions. Tablets battle desktops and laptops even.Mitchel
@Mitchel yes, I am kind of agree with you that there are many devices themselves with different resolutions, but sometime with just mobile, tablet, desktop view may be sufficient depend on use caseDelectation
I agree with @ctrlplusb. I could sniff the device but this is not a future-proved way I think. Too many devices out there (and more in future). A requirement is also to "break" at 767px. All < will render 'Variation 1', < 'Variation2'Roomette
M
6

A very difficult problem to solve in my opinion with a lot of "it depends" based solutions. :)

I am the author of react-sizeme and react-component-queries, two libraries to help with responsive components, and have experienced similar problems as to which you describe in your question. In my experience I've found that coming up with a solution to one of your problems often affects the other. I'll detail what I mean here by describing my experience below...

I had tried to solve your "problem 2" first:

The flickering of rendering due to the default state was something I experienced during my initial creation of the react-sizeme library. react-sizeme is a higher order component which gets the size available to your component and then passes it into your component. Based on the size you can of course choose to render different components as you have done in your example, so update flickering can occur unless you happen to hit a default state sweet spot. I "conquered" this by changing react-sizeme to initially render an empty placeholder in order to get the available width/height and then only render your Component, giving it the "proper" width/height. This has worked very effectively for me. No longer do I see ComponentBob being rendered, only to be unmounted and have ComponentFoo immediately render in it's place.

Then "problem 1" came along...

react-sizeme started to gain popularity and eventually I had a consumer of the library who wished to use it in a server side rendering context. But because of the fix I put in place for problem 1 the server side rendering would produce a lot of blank content (i.e. the placeholders I was talking about). After the payload was delivered to the browser the placeholder logic would kick in and eventually the size data would be sent to the component and it would be rendered. This is not ideal as you essentially nullify any benefit of doing SSR in the first place. I worked with this user and we decided that the best way forward would be to allow for react-sizeme to be configured to run in "SSR Mode". Basically this entailed dropping the placeholder rendering and allowing for a default Component to be rendered so that you don't get blank pages on the initial server response, but then you can easily suffer the component flickering problem again!

Aaaaaaaaaah! The see saw of affect here! :(

So basically solving one problem directly affects the other.


I've continued to give this some thought and I believe that probably the best way to do this is to try and get the users browser width/height on first request. This would essentially mean rendering a simple utility that fishes out this information and then posts it back to the server with the intention of rendering the user's initial request. You could then use the width/height and pass it through your entire component tree (doing math along the way) to keep on determining what the available height/width is for each component. Super tricky stuff here, but could possibly work.

The other peril of course is that google simply indexes a blank page for the initial request (i.e. the blank render of the util that fishes out the initial width/height). You would have to try and look into using some clever HTTP Response codes such as redirects etc in order to ensure that google follows the trail to the proper rendered output.


Sorry, this may not be the answer you were looking for, but hopefully my experience helps in some manner or provides some sort of inspiration. If you do come up with some interesting experiments please do keep me posted. I would be happy to work with you on this.

Mitchel answered 7/6, 2016 at 12:37 Comment(3)
Thanks for the reply @ctrlplusb. It looks like a serious problem to me. The reason why I render on the server is SEO. Because the content is same on 'mobile' and 'desktop', in my case it doesn't really matter what version a crawler reads. The 'SSR mode' you described is basically what I have now - a default state - which comes with the flicker. The idea of using some kind of middleware between server-hit and react-render sounds a bit tricky and also would mean one additional request (slow down performance). I keep you posted here in case I find a workaround.Roomette
Just FYI, I currently don't have much time to figure out a solution for above problem so I went with 'mobile-detect.js' on the server and pass down the header information to the state. This way I can't go adaptive by a specific width, but my people here are happy with it to work like that.Roomette
I'm not a SEO expert, but to me the flicker solution is not a solution, I don't think crawlers (who read js also these days..), are so happy to see a content for an instant and a different content after...Dituri
D
0

You can get user agent from header on server, then dispatch an redux action with user agent information with a reducer that return the data, this way you can detect whether user on mobile phone/tablet/desktop/etc and render accordingly

Delectation answered 7/6, 2016 at 12:41 Comment(3)
I honestly feel that just detecting device is insufficient these days. You need to detect resolution as devices themselves have such varying resolutions. Tablets battle desktops and laptops even.Mitchel
@Mitchel yes, I am kind of agree with you that there are many devices themselves with different resolutions, but sometime with just mobile, tablet, desktop view may be sufficient depend on use caseDelectation
I agree with @ctrlplusb. I could sniff the device but this is not a future-proved way I think. Too many devices out there (and more in future). A requirement is also to "break" at 767px. All < will render 'Variation 1', < 'Variation2'Roomette

© 2022 - 2024 — McMap. All rights reserved.