Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thoughts on server side rendering #46

Closed
edorivai opened this issue Mar 28, 2017 · 13 comments
Closed

Thoughts on server side rendering #46

edorivai opened this issue Mar 28, 2017 · 13 comments

Comments

@edorivai
Copy link
Collaborator

This is intended to share some thoughts, and is somewhat related to #13.

My goal is for desktop specific code not to be downloaded/evaluated on mobile devices and vice versa. This is something that I think would present a significant boost to initial load time for many websites.

Now, a combination of this library and react-loadable would work perfectly fine, if server side rendering is not a requirement. Implementing this combination naively results in both mobile as well as desktop components being rendered serverside, which in turns results in a mismatch on the client. Actually, my current implementation (without this library), involves always rendering both components, and using CSS media queries to hide the irrelevant ones.

I understand that it's fundamentally impossible to perform media queries serverside. However, I think we should be able to do a better job than just rendering everything, always. I'm thinking of using information from the User Agent string as a heuristic to guess which elements to render.

Of course, this approach will inevitably result in mismatches in some cases, for example: User agent signals "mobile", but the device is large enough to show the desktop version according to the media queries. But I think accepting mismatches in these edge cases is worth the speedup in most of the cases.

I didn't dive into implementation details, because I'd like others to chime in, and tell me if they think it's a horrible idea before I'd invest 😛

@mjackson
Copy link
Member

I think the only thing we need to give you on our end is a <Media defaultMatches> prop that you can use to override the default matches state. Then you can get as fancy as you want and we don't even have to know about it.

@mjackson
Copy link
Member

I should clarify: you can think of <Media defaultMatches> like <input defaultValue>. It just sets the initial state.

@edorivai
Copy link
Collaborator Author

edorivai commented Mar 29, 2017 via email

@edorivai
Copy link
Collaborator Author

I think I misunderstood; I thought defaultMatches was already available. But as far as I can tell it is not, right?

But yes, I think defaultMatches would be cool. Just to make sure I understand properly: on the server react-media detects that it cannot resolve any media queries, so it will apply the defaultMatches. We would have to make sure that the initial render on the client uses defaultMatches as well, to prevent client/server mismatch.

@mjackson
Copy link
Member

The change here is really simple, probably only a few lines. Instead of

state = {
  matches: false
}

the <Media> component should do

static defaultProps = {
  // The default value of the defaultMatches prop is false. This means when it
  // is not given, the <Media> component will *not* match by default.
  defaultMatches: false
}

state = {
  // Then we just initialize state using the defaultMatches prop instead
  // of hard-coding `false` here.
  matches: this.props.defaultMatches
}

@edorivai
Copy link
Collaborator Author

edorivai commented Apr 4, 2017

Would you appreciate a PR, or are you planning to look into this yourself for a bit?

@edorivai
Copy link
Collaborator Author

edorivai commented Apr 6, 2017

I've actually got a version working with the exact changes as you outlined them. I can submit a PR if you like, but I have one question. Currently it always matches by default:

state = {
  matches: true
}

You suggest to disable matching by default, which works for me, but might break other peoples' stuff. For me the default doesn't matter, because I will always override the default. What would you prefer defaultProps.defaultMatches to be?

@edorivai
Copy link
Collaborator Author

Awesome. Are you planning to bump the NPM version any time soon?

@mjackson
Copy link
Member

Sure, I can cut a release later today :)

@LoicMahieu
Copy link

Hi @mjackson Are you planning to... bump the NPM version ;) ? Thanks! 🚀

@nezed nezed mentioned this issue Jun 21, 2017
mjackson pushed a commit that referenced this issue Jul 11, 2017
@eahlberg
Copy link

eahlberg commented Feb 4, 2018

@edorivai I'm also interested in this, do you have an example repo somewhere of how you approach this?

@edorivai
Copy link
Collaborator Author

edorivai commented Feb 5, 2018

@eahlberg I don't have an example repo, but the gist is something like this:

  1. Detect device serverside based on User Agent info, using something like https://github.com/faisalman/ua-parser-js
  2. Store device info in a way that your components can access it at render time, we use a redux store.
  3. Render <Media> element, with defaultMatches prop set, based on the device found in (1), example:
const ResponsiveHeader = ({ mobile }) => (
	<Media query="max-width: 480px" defaultMatches={mobile}>
		{matches => (matches ? <HeaderMobile /> : <HeaderDesktop />)}
	</Media>
);

export default connect(state => ({ mobile: state.mobile }))(ResponsiveHeader);

Now the idea is that client side, the browser will be able to resolve the media query (max-width: 480px), however, serverside there won't be any information available on the dimensions of the device, so the query cannot be resolved. In that case, react-media will fall back to whatever you provided in defaultMatches.

There is a drawback; detecting based on user agent info is limited. You can detect the type of device, but determining the actual screen dimensions is hard. Even though you might be able to come a long way by guessing the screen dimensions based on device model (eg "iPhone 6S"), there will always be things like portrait/landscape orientation, and manual window resizing to mess up your guess. And if you guess is wrong, you'll end up with a mismatch between client/server renders. In my experience, if your breakpoints aren't too close together, determining which version (mobile vs desktop) to render on the server is worth it.

@eahlberg
Copy link

eahlberg commented Feb 5, 2018

@edorivai alright, sounds like a good approach. Will see if it fits for my use case, but thank you very much for taking the time to explain!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants