facebook / relay

Relay is a JavaScript framework for building data-driven React applications.
https://relay.dev
MIT License
18.29k stars 1.81k forks source link

How do we do server-side rendering with Relay Modern? #1881

Open taion opened 7 years ago

taion commented 7 years ago

This is sort of a follow-up to https://github.com/facebook/relay/issues/1687 and https://github.com/facebook/relay/pull/1760.

What is a recommended pattern for handling server-side rendering in a simple request-response paradigm with Relay Modern?

RecordStore seems to expose capabilities for serializing and deserializing its contents, and I can use a preloaded RecordStore when building a new Environment, but this is a bit deceptive, since without https://github.com/facebook/relay/pull/1760, my <QueryRenderer>s will ignore the preloaded records... but this capability must be there for a reason?

But thinking about this more, I'm not even sure the approach in #1760 is correct. For a streaming network layer, it seems like it would be wrong to omit sending out the request just because I have preloaded data – instead I'd just want the server to e.g. not send me the initial payload (because I already have it).

Something like the query response cache seems more promising, but the bundled one doesn't really expose hooks for exporting its contents, plus a key/value cache with a TTL isn't exactly the right model here anyway.

Am I missing something?

wasd171 commented 4 years ago

After playing around I was able to get it working (non-hooks version) in Next.js with the following technique:

On the server I do:

const records = environment.getStore().getSource() stringifiedRecords = JSON.stringify(records)

const operation = createOperationDescriptor( getRequest(initialProps.gqlQuery), initialProps.gqlVariables ) const snapshot = environment.lookup(operation.fragment, operation) initialProps.gqlResponse = snapshot.data

And the `QueryRenderer` looks like the following:

<QueryRenderer environment={environment} query={gqlQuery} variables={gqlVariables} dataFrom="STORE_THEN_NETWORK" render={({ props, error }) => { const data = props || gqlResponse // gqlResponse comes from snapshot.data

            console.log('props', !!props)
    console.log('response', !!gqlResponse)

    if (error) {
        return null
    } else if (data) {
        return <Feed data={data} />
    } else {
        return null
    }
}}

/>

I get the following logs:
+ server

props false response true

+ client

props true response true

Changing `dataFrom` to `"NETWORK_ONLY"` results in the same output for the client and server:

props false response true

robrichard commented 4 years ago

You can try something like this, rendering ReactRelayContext.Provider instead of rendering a <QueryRenderer>.

// on server fetch data
const data = await fetchQuery(
    environment,
    initialProps.gqlQuery,
    initialProps.gqlVariables
)

// on client
const operation = createOperationDescriptor(
    getRequest(initialProps.gqlQuery), 
    initialProps.gqlVariables
)
const data = environment.lookup(operation.fragment, operation).data;

// server and client
<ReactRelayContext.Provider value={{environment}}>
    <Feed data={data} />
</ReactRelayContext.Provider>
wasd171 commented 4 years ago

@robrichard this works like a charm!

snrbrnjna commented 4 years ago

pretty sure, that the problems here are solved with this PR https://github.com/facebook/relay/pull/2883

matthieu-foucault commented 4 years ago

@wasd171 any chance you could share a working example?

maraisr commented 4 years ago

@wasd171 how did you get this not to blow out of control over time, or is the source, store and environment re-created per request?

const records = environment.getStore().getSource() stringifiedRecords = JSON.stringify(records)

sibelius commented 4 years ago

@robrichard do you have a wrapper component that handle this behavior different on server vs on client?

sibelius commented 4 years ago

I've wrote a blog post how I make Relay work well with SSR

https://dev.to/sibelius/adding-server-side-rendering-to-a-relay-production-app-30oc https://medium.com/@sibelius/adding-server-side-rendering-to-a-relay-production-app-8df64495aebf

anybody wanna take a stab to add this to the official docs?

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

koistya commented 3 years ago

Here is yet another example of how to hydrate/dehydrate Relay store at CDN edge locations using Cloudflare Workers.

https://github.com/kriasoft/nodejs-api-starter/pull/277 See web/main.ts, web/core/router.ts, web/proxy/index.ts, web/proxy/transform.ts.

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.