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

Example of an SSR RNWeb adaptive/heavily responsive site #1688

Closed
elmpp opened this issue Jul 29, 2020 · 23 comments
Closed

Example of an SSR RNWeb adaptive/heavily responsive site #1688

elmpp opened this issue Jul 29, 2020 · 23 comments

Comments

@elmpp
Copy link

elmpp commented Jul 29, 2020

Hi there,

At my place of work we have a large RN codebase and have been working hard on the web platform side of things. We have a very high level of code reuse made possible by RNWeb (thanks). We're doing SSR (using Next) for SEO reasons mostly

Here's the rub - almost everyone approaches this web platform work as if it's a standard responsive website ("let's use media queries", "let's lazy load all components") and i'm not doing a good job communicating the differences particularly with regard to the RNWeb "atomic CSS-in-JS" styling of RNWeb vs standard CSS, especially with SSR thrown into the mix

Using the Twitter site as an example helped loads but as it's CSR it doesn't cover all bases and we're now stuck in the depths of lazy loading vs "full SSR" (with vary headers ) discussion.

Are there any good examples of an SSR RNWeb site out there? Bonus points if they go full SSR and perhaps communicate the client dimensions etc via cookies or other method.

Thanks

@steida
Copy link

steida commented Jul 29, 2020

As for SSR media queries: https://github.com/artsy/fresnel I believe this is the only reliable approach.

@elmpp
Copy link
Author

elmpp commented Jul 29, 2020

Rendering all permutations of our responsive breakpoints/orientations serverside isn't an option i'm afraid

@steida
Copy link

steida commented Jul 29, 2020

@elmpp Then you have no other option.

@elmpp
Copy link
Author

elmpp commented Jul 29, 2020

There are plenty of options and it's kinda why i'm asking the question

I'm not looking for another discussion of the merits of each, just examples please (it's the reason for the question)

@necolas
Copy link
Owner

necolas commented Jul 29, 2020

Any React DOM app using those SSR techniques is an example. It's not usually a good idea for apps to do "full SSR". The new Facebook app doesn't even do that, even though it has chosen to rely on SSR. You still want to lazy load expensive assets that aren't needed for the initial render

@elmpp
Copy link
Author

elmpp commented Jul 29, 2020

Yeah the majority of components probably will be lazy loaded (user-specific data) but we're finding there is another class of components that need to be adaptive and would be best rendered ServerSide.

Examples would be a our navigation which hits an api for its items but looks and operates quite differently mobile/desktop

@elmpp
Copy link
Author

elmpp commented Jul 29, 2020

Any React DOM app using those SSR techniques is an example

I guess so but RNWeb's atomic CSS-in-JS means the media queries can't be generated and sent down initially - it has to "pick" which can lead to jank for those prerendered components

Anyway, thanks for the reminder about facebook.com - it does actually render entirely differently based on UserAgent so it's just the example i needed (and stores it in cookies for subsequent visits) 👍

@necolas
Copy link
Owner

necolas commented Jul 29, 2020

SSR techniques don't generate media queries either. The only good thing about CSS media queries is that a SSR response doesn't need to know anything about the viewport dimensions. The problem with CSS media queries is how little you can do with them in the first place since they have no relation to the DOM (unlike JS media queries). They're completely deficient when it comes to creating dynamic apps that adapt to their environment, supporting everything from a portrait phone to a widescreen desktop screen. Even using client hints it's pretty complicated to get a SSR web app to render as if it were in the browser. At the moment there is probably no benefit for SSR of a React app, because you pay the cost of rendering on both the server and the client. The initial response isn't even interactive. Plus, using a PWA architecture would let you cut out the server roundtrip for assets anyway.

@javascripter
Copy link
Contributor

javascripter commented Jul 30, 2020

At my place of work we have a large RN codebase and have been working hard on the web platform side of things. We have a very high level of code reuse made possible by RNWeb (thanks). We're doing SSR (using Next) for SEO reasons mostly

I have a similar experience at my workplace. We had a large RN codebase first and needed to do the web at a later stage.
I first chose RNW + Next.js and artsy/fresnel to do full SSR for SEO (we have hundreds of thousands of dynamic pages).

Using artsy/fresnel with RNW in next.js had a few problems:

  1. the lib appends div as a wrapping element in the DOM, which is not a RNW component so you need to apply hacky styles to the wrapper div at various places if you combine them
  2. the lib provides no easy way to apply responsive inline styles to a component (e.g. <View style={[isMobile && { color: 'red' }] />)
  3. It increased the output size (it wasn't too much of a problem at that time)

2 was not easy to solve, so in some places where SEO didn't matter, we simply code-splitted some part and did CSR there because having SP and PC components separately led to so much duplicate code.

I however later decided to ditch Next.js and SSR altogether because the added complexity of SSR hurt our team productivity and decided it wasn't worth it (responsive design, developing for both client/server, using lib limited to SSR compatible, not exposing credentials to shared pages, etc all added extra cognitive overhead).

I migrated the app to create-react-app + hand-written small useResponsive() utility, GoogleChrome/Rendertron to pre-render for google bots (I pre-render only mobile version).
In my observation our transition to CSR didn't affect SEO negatively after all. In contrast our team productivity increased a lot as a result. it's not going to be as fast as proper SSR, but IMO it's a decent trade off for some use cases.

A bit off-topic: I faced one issue when I set up Rendertron though: CSS wasn't picked up properly. I believe it's due to RNW's optimized CSSOM? @necolas. I added a script to copy RNW style into a dummy style tag for HeadlessChrome UserAgent to solve this.

@elmpp
Copy link
Author

elmpp commented Jul 30, 2020

Even using client hints it's pretty complicated to get a SSR web app to render as if it were in the browser. At the moment there is probably no benefit for SSR of a React app, because you pay the cost of rendering on both the server and the client. The initial response isn't even interactive. Plus, using a PWA architecture would let you cut out the server roundtrip for assets anyway.

Yeah - the I agree distinction is correct there for an app where the true value is after the client initialisation but for a more presentational site the more a site can be SSR'd the better (for example a movie details page with "related movies" sidebar). The difference in UX can be quite stark for this kind of audience

Another big factor that isn't thought of so much is the ability to cache - if you're rendering large portions of the site during SSR and then caching in Cloudflare or wherever you're reducing load on both Server and API

@elmpp
Copy link
Author

elmpp commented Jul 30, 2020

  1. the lib appends div as a wrapping element in the DOM, which is not a RNW component so you need to apply hacky styles to the wrapper div at various places if you combine them
  1. the lib provides no easy way to apply responsive inline styles to a component (e.g. <View style={[isMobile && { color: 'red' }] />)
  2. It increased the output size (it wasn't too much of a problem at that time)

We have a similar situation whereby another styling solution choice (Radium) is incompatible with RNWeb leading to all the components needing to be wrapped. This definitely feels like you're fighting things at that point so i understand you choosing a different path

I however later decided to ditch Next.js and SSR altogether because the added complexity of SSR hurt our team productivity and decided it wasn't worth it (responsive design, developing for both client/server, using lib limited to SSR compatible, not exposing credentials to shared pages, etc all added extra cognitive overhead).

Yeah, the extra complexity really is a massive factor. Only thing i'd say in its defence is that it is getting easier (@EvanBacon @brunolemos have helped loads with the 100% reuse dream) and when it works well it can allow a very small number of devs create a product for all platforms with proven SEO + great UX (can you imagine facebook.com lazy loading the main feed now it's done SSR?!)

@necolas
Copy link
Owner

necolas commented Aug 7, 2020

Radium's styling implementation is incredibly slow and should be avoided.

I think this question has been answered. There currently isn't a way to define static CSS media queries with this API, and that prevents certain responsive layouts from being possible via SSR before the client initializes

@necolas necolas closed this as completed Aug 7, 2020
@RichardLindhout
Copy link
Contributor

RichardLindhout commented Aug 19, 2020

@necolas Is it possible to expose the styleResolver as unstable_styleResolver this would help me a great deal to make some kind of library which would work on the server too.

E.g.

 <CrossResponsive
            style={styles.categoryHeader}
            styleIfLargerThan={{
              width: {
                700: styles.categoryHeaderDesktop,
              },
            }} />

To make this work on the server I would like to use the styleResolver to generate all combinations of styles as hardcoded classNames and add them later outside of react based on screen width/height with screen listener in javascript outside of React.

@necolas
Copy link
Owner

necolas commented Aug 19, 2020

I don't understand why you'd need that API to do what you're saying. Sounds like you can already do that now. But if you're using JS media queries that's not going to really work for SSR anyway

@RichardLindhout
Copy link
Contributor

It can if you load the script tag directly in the html instead of via React

@RichardLindhout
Copy link
Contributor

But that would require generating the classNames for all possible configurations so I know outside of React which classnames to add based on width/height.

E.g. I would have a JSON in my script tag like this

<script>
 const styleIfLargerThan  = { width: { 700: "rn929 rn 292 rn292" }}
let element = getElementbyUniqueID('CrossResponsive9293002')
const screenWidth = window.screenWidth
const applyClasses = Object.keys(styleIfLargerThan.width).filter(width => screenWidth > Number(width)).map(width => styleIfLargerThan[width])
element.classes.add(applyClasses)
</script>

This would run before React hydrates the view so the user does not see any flickering

@RichardLindhout
Copy link
Contributor

Ofcourse this approach would only be done in SSR and client-side still in a normal way without any internal api's needed

@necolas
Copy link
Owner

necolas commented Aug 19, 2020

That's currently a very fragile approach. I'm not going to expose any internals to support that, so you can reach into the modules at your own risk. I happen to be working on another framework that could support this, but the problem space overlaps with SSR streaming and is something that should really be automatically handled by a framework that explicitly supports these features.

@necolas
Copy link
Owner

necolas commented Aug 19, 2020

You can also use dataSet to connect styles from outside the framework to data-* props. Obviously won't be compatible with RN though. And you lose the deterministic rendering guarantees too

@RichardLindhout
Copy link
Contributor

I don't think that's what I'm looking for, but I dont use SSR that much and our app had much of the same styling across mobile and desktop. Only one place where I needed this today.

Maybe if I feel like I'll hack a together a small demo with demo of this approach so we can discuss it further

@KeitelDOG
Copy link

I have the same Problem, but I'm wondering if SSR has to be full SSR. I think, from SEO point of view, a bit of client codes to fix rendering is not a real issue, as long as it does not create too much vertical and horizontal offset. Because Google SEO does execute JS for a certain time to allow complete rendering.

I could just rerender the difference at client side under useEffect. But @artsy/fresnel seems to put things closer for SSR.

@nandorojo
Copy link
Contributor

SSR techniques don't generate media queries either. The only good thing about CSS media queries is that a SSR response doesn't need to know anything about the viewport dimensions. The problem with CSS media queries is how little you can do with them in the first place since they have no relation to the DOM (unlike JS media queries). They're completely deficient when it comes to creating dynamic apps that adapt to their environment, supporting everything from a portrait phone to a widescreen desktop screen. Even using client hints it's pretty complicated to get a SSR web app to render as if it were in the browser. At the moment there is probably no benefit for SSR of a React app, because you pay the cost of rendering on both the server and the client. The initial response isn't even interactive. Plus, using a PWA architecture would let you cut out the server roundtrip for assets anyway.

@necolas I really appreciate your thoughts on this, and I totally understand your opinion on the fragility of CSS media queries.

With React 18's SSR + Suspense and Server Components approaching, it seems that the React team is investing heavily in SSR.

Do you anticipate any changes to RNW and its view on media queries as a result?

Thank you!

@finkef
Copy link

finkef commented Nov 20, 2021

Trying to solve the problems of SSR with responsive styles, I just published react-native-tailwind.macro which allows you to write responsive styles based on Tailwind that get converted to CSS media queries on the web.

Under the hood, the macro extracts and compiles all the Tailwind styles and uses react-native-media-query to produce CSS media queries that are applied to elements with a generated dataSet prop (as @necolas mentioned here). However, this only works reliably here because with Tailwind we only accept (uninterpolated) strings and know all the styles at build time.

Basically what it does is turn this:

const Comp = () => <View tw="w-[100px] h-[100px] bg-blue-500 md:bg-purple-500 lg:bg-pink-500" />

into this:

const useTailwindStyles = createUseTailwindStyles({
  /* statically generated RN styles */
})

/**
 * This gets added to the CSS stylesheet:
 *
 * [data-media~="rnmq-f324bd"]: {
 *   background-color: rgb(59, 130, 246);
 *   height: 100px;
 *   width: 100px;
 * }
 *
 * @media (min-width: 768px) [data-media~="rnmq-f324bd"]: {
 *   background-color: rgb(139, 92, 246) !important;
 * }
 *
 * @media (min-width: 1024px) [data-media~="rnmq-f324bd"]: {
 *   background-color: rgb(236, 72, 153) !important;
 * }
 */

const Comp = () => {
  const _tailwindStyles = useTailwindStyles()

  return (
    <View
      style={_tailwindStyles["randomId"]}
      dataSet={{ media: "rnmq-f324bd" }}
    />
  )
}

On the web, the View then uses the styles matched by the [data-media~="rnmq-f324bd"] selector. On native, everything behaves as usual. Some more details are here.

I'm curious if this approach works for other libs as well, but I think statically extracting styles based on JS objects (that might contain non literal stuff, like references to variables, etc.) might be quite tough (but not necessarily impossible 🤷 ).

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

No branches or pull requests

8 participants