Adds server side rendering support to React Relay

Overview

Isomorphic React Relay npm version

Enables server-side rendering of React Relay containers.

If you use react-router-relay you might also become interested in isomorphic-relay-router.

Acknowledgments

Thank you to everyone who helped in the development of this project with suggestions, testing, reported issues, pull-requests. Thank you to the Facebook employees who reviewed my contributions to Relay, which helped to improve the server-side rendering support.

Installation

npm install --save isomorphic-relay

How to Use

Here is an example with detailed comments of how isomorphic-relay can be used on the server:

import IsomorphicRelay from 'isomorphic-relay';

const rootContainerProps = {
  Container: MyContainer,
  queryConfig: new MyRoute(),
};

app.get('/', (req, res, next) => {
  // Create a Relay network layer. Note that on the server you need to specify
  // the absolute URL of your GraphQL server endpoint.
  // Here we also pass the user cookies on to the GraphQL server to allow them
  // to be used there, e.g. for authentication.
  const networkLayer = new Relay.DefaultNetworkLayer(
    'http://localhost:8080/graphql',
    { headers: { cookie: req.headers.cookie } },
  );

  // Use IsomorphicRelay.prepareData() to prefetch the data required for
  // rendering of the Relay container.
  IsomorphicRelay.prepareData(rootContainerProps, networkLayer).then(({ data, props }) => {
    // Use 
   
     to render your Relay container when the data is ready.
   
    // Note that we cannot use the standard 
   
     because at the first render
   
    // it renders an empty/loading screen even when all the required data is already available.
    // Unlike that, 
   
     in that case renders normally right at
   
    // the first render, and it is important for server side rendering
    // where we do not have a second render.
    const reactOutput = ReactDOMServer.renderToString(
      <IsomorphicRelay.Renderer {...props} />
    );

    // To allow the data to be reused in the browser, serialize and embed it
    // in the page together with the React markup.
    res.render('index.ejs', {
      preloadedData: JSON.stringify(data),
      reactOutput
    });
  }).catch(next);
});

And here is an example of the code that can be used in the browser:

import IsomorphicRelay from 'isomorphic-relay';

const environment = new Relay.Environment();
environment.injectNetworkLayer(new Relay.DefaultNetworkLayer('/graphql'));

// Deserialize the data preloaded on the server.
const data = JSON.parse(document.getElementById('preloadedData').textContent);

// Use IsomorphicRelay.injectPreparedData() to inject the data into the Relay cache,
// so Relay doesn't need to make GraphQL requests to fetch the data.
IsomorphicRelay.injectPreparedData(environment, data);

// Use IsomorphicRelay.prepareInitialRender() to wait until all the required data
// is ready for rendering of the Relay container.
// Note that it is important to use the same rootContainerProps as on the server to
// avoid additional GraphQL requests.
IsomorphicRelay.prepareInitialRender({ ...rootContainerProps, environment }).then(props => {
  // Use 
   
     to render your Relay container when the data is ready.
   
  // Like on the server we cannot use the standard 
   
    , bacause here
   
  // we also need to render normally right at the initial render, otherwise we would get
  // React markup mismatch with the markup prerendered on the server.
  ReactDOM.render(<IsomorphicRelay.Renderer {...props} />, document.getElementById('root'));
});

Also see the Star Wars example.

Comments
  • Question: how do you flush relay store?

    Question: how do you flush relay store?

    Hello! In example we can see that relay instance lives in persistent scope.

    Does it mean that query data will be cached in node memory and shared between requests?

    Or does this line const storeData = RelayStoreData.getDefaultInstance(); create an empty instance for each request?

    enhancement 
    opened by droganov 23
  • Expected prop to be data fetched by Relay

    Expected prop to be data fetched by Relay

    Hi, after i upgraded to isomorphic-relay v0.5.1 All my components started to complain asking that the data has to be data fetched by relay.

    Warning: RelayContainer: Expected propmesupplied toNavbarto be data fetched by Relay. This is likely an error unless you are purposely passing in mock data that conforms to the shape of this component's fragment.

    screen shot 2016-02-15 at 22 40 18

    Any idea about this? After hours of debugging still i couldn't find the reason

    usage error 
    opened by fenos 22
  • Isomorphic JWT authentication

    Isomorphic JWT authentication

    Hi @denvned, I'm focusing my project on this package which provide isomorphism to relay. This is not a question related to the package "Which works great" but more related to the isomorphism world and authentication. I'm writing here because i believe you may suggest something very precious to me and community regarding this subject.

    In my current project I want to achieve the following criteria with an isomorphic app:

    • Have my GraphQL end points accepting only JWT authentication and be stateless
    • Have my isomorphic app aware of user authentication when they refresh the page (with security in mind)

    To solve this I needed 2 authentications, one for the isomorphic app "session based" and the second for the GraphQL server which is the wanted JWT.

    As following I created a diagram that describe this authentication flow: note: it start from the browser requesting authentication token to graphQL

    iso authentication

    The biggest downside of this approach "The reason why i'm posting here" is the way how I pass the token to Javascript / React. I'm actually rendering the token in a div Similar way how we preload the data with relay "It render the data as a JSON string in a HTML element"

    On DOM loaded i extract the token information from the <div id="auth">{"token":"usertoken"}</div> and pass it to a AuthStore.js which keep it in memory and pass it to Relay for sub-sequential request.

    I implemented this approach and it works just fine, but i'm concerned about security of rendering the token in the div. If you have any better idea to pass this token to Javascript would be great. Or did you ever done a similar authentication with a Isomorphic app?

    Thanks for the time to read this

    question 
    opened by fenos 17
  • Still necessary to use injectBatchingStrategy?

    Still necessary to use injectBatchingStrategy?

    I am getting errors very similar to those in this issue - https://github.com/denvned/isomorphic-relay-router/issues/5#issuecomment-156822389

    I have taken all of the upgrades for isomorphic-relay and saw that the usage of injectBatchingStrategy was removed from the README. Is it perhaps still required for some cases?

    question 
    opened by jeromecovington 12
  • Multiple injectPreparedData issue

    Multiple injectPreparedData issue

    I am passing two sets of data to two separate calls to injectPreparedData in one view, like so:

    IsomorphicRelay.injectPreparedData(relayInfinite);
    IsomorphicRelay.injectPreparedData(relayBreaker);
    

    ...yet I am seeing the following error: image

    All works (on server and client) when I only call injectPreparedData once on one set of relay data, like so:

    IsomorphicRelay.injectPreparedData(relayInfinite);
    

    I also tried merging both sets of data and calling injectPreparedData once, like so:

    IsomorphicRelay.injectPreparedData([...relayInfinite, ...relayBreaker]);
    

    ...but then I still get the Uncaught TypeError: dataID.substring is not a function error, as above.

    invalid 
    opened by jeromecovington 12
  • injectPreparedData on the server side?

    injectPreparedData on the server side?

    Can prepareData accept on optional environment so that injectPreparedData can be called on the server side if some data is known to already be available, such as from an upstream service?

    If not provided, environment can still default to new Relay.Environment().

    If this seems like a reasonable approach, I can make a PR.

    opened by jakepusateri 10
  • Query running on both client and server.

    Query running on both client and server.

    I'm a little confused with what's going on here, but my GraphQL query is being run twice - once on server and once on the client; even though I'm following the example. The prepared data is being correctly serialized out to the rendered page and then deserialized for use with injectPreparedData, but the client still makes the query. My understanding is that because all the query data should be in the cache, the client doesn't need to run a query of its own.

    opened by AndrewIngram 10
  • Upgrade to Relay 0.8

    Upgrade to Relay 0.8

    This is more of a question at this point. Since Relay 0.8 is released:

    https://github.com/facebook/relay/releases/tag/v0.8.0

    will a new version of isomorphic-relay be required?

    opened by thelordoftheboards 10
  • Getting 'Encountered error:

    Getting 'Encountered error: "ReferenceError: self is not defined"'

    I know this error is supposed to come from relay on the server side, and that IsomorphicRelay is supposed to have some hack to work around it, but I'm actually getting this error as soon as I add

    import IsomorphicRelay from 'isomorphic-relay';
    

    even without importing relay itself. Working with the most recent version from npm - 0.4.2 If I remove the IsomorphicRelay import the error goes away (but obviously I do need it as I'm trying to play with relay and server side rendering).

    If it helps at all, the first few lines of the server side js bundle are:

    /******/ (function(modules) { // webpackBootstrap
    /******/        // The module cache
    /******/        var installedModules = {};
    
    /******/        // The require function
    /******/        function __webpack_require__(moduleId) {
    
    /******/                // Check if module is in cache
    /******/                if(installedModules[moduleId])
    /******/                        return installedModules[moduleId].exports;
    
    /******/                // Create a new module (and put it into the cache)
    /******/                var module = installedModules[moduleId] = {
    /******/                        exports: {},
    /******/                        id: moduleId,
    /******/                        loaded: false
    /******/                };
    
    /******/                // Execute the module function
    /******/                modules[moduleId].call(module.exports, module, module.ex
    ports, __webpack_require__);
    
    /******/                // Flag the module as loaded
    /******/                module.loaded = true;
    
    /******/                // Return the exports of the module
    /******/                return module.exports;
    /******/        }
    
    
    /******/        // expose the modules object (__webpack_modules__)
    /******/        __webpack_require__.m = modules;
    
    /******/        // expose the module cache
    /******/        __webpack_require__.c = installedModules;
    
    /******/        // __webpack_public_path__
    /******/        __webpack_require__.p = "/webpack/";
    
    /******/        // Load entry module and return exports
    /******/        return __webpack_require__(0);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */
    /***/ function(module, exports, __webpack_require__) {
    
            'use strict';
    
            var _isomorphicRelay = __webpack_require__(232);
    
            var _isomorphicRelay2 = _interopRequireDefault(_isomorphicRelay);
    
            var _reactOnRails = __webpack_require__(1);
    
            var _reactOnRails2 = _interopRequireDefault(_reactOnRails);
    
            function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    
            var stringify = __webpack_require__(447);
    
            var React = __webpack_require__(171);
    
    question 
    opened by shaimo 9
  • React render not resolving the diff in server vs. client graphql response

    React render not resolving the diff in server vs. client graphql response

    Hi, I've got isomorphic-relay working nicely. I am finding however, that while the component renders in the html payload from the server, that when the relay query subsequently runs in the client, it triggers a re-render...effectively removing the server markup from the dom and replacing it with the result from the client, even though they are essentially the same. Do you have any help or pointers on how to avoid this needless resolution of a diff (that should be a "non-diff") in the vdom?

    usage error 
    opened by jeromecovington 8
  • Bundling webpack with IsomorphicRelay

    Bundling webpack with IsomorphicRelay

    I am using isomorphic relay and integrated webpack for the node server with babel and the relay-babel-plugin. It all works well when I load the page the first time, the second one it crashes with the following error.

    ReferenceError: document is not defined
        at getActiveElement (/Users/AJ/Desktop/winebox/app/build/index.js:17061:13)
        at ReactReconcileTransaction.ReactInputSelection.getSelectionInformation (/Users/AJ/Desktop/winebox/app/build/index.js:16650:24)
        at ReactReconcileTransaction.Mixin.initializeAll (/Users/AJ/Desktop/winebox/app/build/index.js:4477:76)
        at ReactReconcileTransaction.Mixin.perform (/Users/AJ/Desktop/winebox/app/build/index.js:4444:13)
        at ReactUpdatesFlushTransaction.Mixin.perform (/Users/AJ/Desktop/winebox/app/build/index.js:4445:21)
        at ReactUpdatesFlushTransaction.assign.perform (/Users/AJ/Desktop/winebox/app/build/index.js:3677:39)
        at Object.flushBatchedUpdates (/Users/AJ/Desktop/winebox/app/build/index.js:3738:20)
        at Object.wrapper [as flushBatchedUpdates] (/Users/AJ/Desktop/winebox/app/build/index.js:3983:22)
        at ReactDefaultBatchingStrategyTransaction.Mixin.closeAll (/Users/AJ/Desktop/winebox/app/build/index.js:4511:26)
        at ReactDefaultBatchingStrategyTransaction.Mixin.perform (/Users/AJ/Desktop/winebox/app/build/index.js:4458:17)
    

    I have it narrowed down to the portion that creates the reactOutput in the render function.

    const reactOutput = ReactDOMServer.renderToString(
          <IsomorphicRelay.RootContainer {...rootContainerProps} />
        );
    

    Is there a way to fix this? Thanks in advance for any help

    usage error 
    opened by alewaros 7
  • Is it possible to complete reload of component data on the browser ?

    Is it possible to complete reload of component data on the browser ?

    In the list of router component passed to Isomorphic Router, I need one or two components to be loaded on the browser after user login. Is there any way I can do that while using Isomorphic-relay.

    opened by softglance 0
  • Passing preloadedRequests to prepareData for queries with aliased parameterized fields

    Passing preloadedRequests to prepareData for queries with aliased parameterized fields

    Related to #50

    Passing preexisting query-payload pairs to prepareData() is messy when the query contains aliased parameterized fields, because Relay modifies the field names to prevent name collisions. To make a preexisting query-response pair for a query containing aliased parameterized fields, I need to use a Relay internal function generateRQLFieldAlias() to produce a valid response. This what I’m doing:

    Example query:

    const examsQueryNode = Relay.QL`
    query ExamsRoute {
        exams {
            SPRING2017: examAverage(term: "SPRING2017")
        }
    }
    `;
    

    Function to generate response for query-response pair:

    import generateRQLFieldAlias from 'react-relay/lib/generateRQLFieldAlias';
    
    function createResponse( examsData ) {
        const preloadedResponse = { exams: {} };
        preloadedResponse.exams[generateRQLFieldAlias('examAverage.SPRING2017.term(SPRING2017)')] = examsData.SPRING2017;
        return preloadedResponse;
    }
    

    Does it seem reasonable to submit a pull request to Relay to make a public interface to the generatedRQLFieldAlias() function for this purpose?

    opened by edorainos 0
  • Differences between Relay.Renderer and IsomorphicRelay.Renderer

    Differences between Relay.Renderer and IsomorphicRelay.Renderer

    Thank you for the excellent work on isomorphic-relay and isomorphic-relay-router

    I am trying to understand how the implementation of IsomorphicRelay.Renderer is different to Relay.Renderer.

    • They both wrap RelayReadyStateRenderer which is a private api (it's not mentioned in the docs)
    • IsomorphicRelay.Renderer is not rendering the initial loading screen to prevent duplicate renderings on the server
    • Relay.Renderer is referencing the Garbage Collector and implements other things that IsomorphicRelay.Renderer doesn't implement (I am not familiar enough with Relay to know what these things do).

    Are there any known inconsistencies/limitations in using IsomorphicRelay.Renderer in place of Relay.Renderer?

    I can tell from the history of the relay repo that there has been previous attempts to make Relay.Renderer isomorphic. This makes me think that isomorphic-relay is a stepping stone to native isomorphic rendering in relay. Is this correct? If so, are there current blockers to make this happen?

    question 
    opened by coolov 1
  • Changelog

    Changelog

    Hey @denvned! Thanks for everything you're doing with isomorphic-relay. I've been checking it out and it's awesome. Would it be possible for you to have a changelog with isomorphic-relay and isomorphic-relay-router? It would make following what needs to be changed a lot easier :)

    task 
    opened by juhaelee 5
Owner
Denis Nedelyaev
Denis Nedelyaev
React ESI: Blazing-fast Server-Side Rendering for React and Next.js

React ESI: Blazing-fast Server-Side Rendering for React and Next.js React ESI is a super powerful cache library for vanilla React and Next.js applicat

Kévin Dunglas 624 Sep 23, 2022
HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API.

React Document Meta HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API. Built wi

kodyl 320 Jun 14, 2022
Studies about Software Architeture in React - basics of server-side rendering, state management and code splitting

Studies about Software Architeture in React - basics of server-side rendering, state management and code splitting

Jéssica Félix 2 Mar 23, 2022
Redux bindings for client-side search

redux-search Higher-order Redux library for searching collections of objects. Search algorithms powered by js-worker-search. Check out the live demo a

Brian Vaughn 1.4k Aug 25, 2022
Complete token authentication system for react + redux that supports isomorphic rendering.

Simple, secure authentication for react + redux TL;DR - View the Live Demo You can see a complete working example here. The code for the demo is here.

Lynn Dylan Hurley 2.1k Sep 19, 2022
Our SDK for the 3D rendering platform React-Three-Fiber.

Zappar for React Three Fiber This library allows you use Zappar's best-in-class AR technology with content built using the 3D rendering platform React

Zappar 29 Sep 19, 2022
✉️ Display e-mails in your React.js projects. (Targets Gmail rendering.)

react-letter is a React.js component that allows for an easy display of HTML e-mail content with automatic sanitization. Support for features should m

Mat Sz 226 Aug 29, 2022
An example using universal client/server routing and data in React with AWS DynamoDB

react-server-routing-example A simple (no compile) example of how to do universal server/browser rendering, routing and data fetching with React and A

Michael Hart 300 Aug 25, 2022
OpenSea-telegrambot - React application with Express server

React application with Express server This project send message to telegram if t

Lovely 1 Feb 4, 2022
:tada: React Universal Hooks : just use****** everywhere (Functional or Class Component). Support React DevTools!

react-universal-hooks React Universal Hooks : just use****** everywhere. Support React >= 16.8.0 Installation Using npm: $ npm install --save react-un

Salvatore Ravidà 175 Aug 1, 2022
React project with Google Firebase API, create partys with admin view and user view. Users can ask topics to admin, and support other topics.

?? Tecnologias Esse projeto foi desenvolvido com as seguintes tecnologias: React Firebase TypeScript ?? Como executar Clone o projeto e acesse a pasta

Felipe Souza 3 Aug 27, 2021
Compares Discord libraries and their support of new API features. Made with React, Next.js, and Bulma.

Compares Discord libraries and their support of new API features. Made with React, Next.js, and Bulma.

Advaith 103 Sep 26, 2022
Lightweight auth library based on oidc-client for React single page applications (SPA). Support for hooks and higher-order components (HOC).

Lightweight auth library based on oidc-client for React single page applications (SPA). Support for hooks and higher-order components (HOC).

null 151 Sep 29, 2022
🌐React and Next.js Snippets with TypeScript support as well

??React and Next.js Snippets with TypeScript support as well

buidler's hub 6 May 18, 2022
A straight forward text component with tooltip support when it's truncated

A straight forward text component with tooltip support when it's truncated

Neo Nie 19 Sep 8, 2022
A better JSON differ & viewer, support LCS diff for arrays and recognise some changes as modification apart from simple remove+add.

A better JSON differ & viewer, support LCS diff for arrays and recognise some changes as modification apart from simple remove+add.

Rex Zeng 34 Sep 5, 2022
Number pairing functions with support for BigInt numbers.

Pairing Number pairing functions with support for BigInt numbers. Features Simple API; Works with plain numbers or BigInt; Extensive tests; Typescript

Causaly 2 Mar 16, 2022
A Remix stack setup to run on Deno with support for Rust WASM modules!

Remix + Deno + Rust -> Webassembly - The Air Metal Stack Welcome to the Air Metal Stack for Remix! ?? + ?? This stack is a good choice if you want to

Ben Wishovich 55 Sep 8, 2022
Like useRef, but with lifecycle and ref merging support

useLifecycleRef Like useRef, but with lifecycle and ref merging support Quick Look Here is a simplfied demonstration on how easy to use useLifecycleRe

Billy Kwok 1 Jun 10, 2022