question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

infra for verifying server side rendering of components

See original GitHub issue

We need an infrastructure to ensure that all the components we build work with server side rendering out of the box. As of today, we write unit tests that use jsdom so essentially it tests the browser environment only, on the other side we use storybook to render our components built on top of Create React App which again is client-side rendering only.

Issue Analytics

  • State:closed
  • Created 10 months ago
  • Reactions:1
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

3reactions
kamleshchandnanicommented, Nov 18, 2022

tried, tested and updated the section above 🚀

3reactions
kamleshchandnanicommented, Nov 18, 2022

I started exploring this and there 2 approaches:

  1. Make storybook capable of doing SSR
  2. Write tests for this and test it as part of unit test workflow

Option 1 isn’t possible with the current architecture of storybook so that is out of the list.

Option 2 has multiple angles to it: So basically the way react-testing-library along with jest works is that it uses jsdom under the hood which is basically an in-memory dom now since it’s an in-memory dom it mimics the browser. Because of this we really can’t test out if the components will fail when it uses window or document instances since those objects will be available in the jsdom environment. As of today basically we write tests as follows

import { render } from '@testing-library/react';

describe('<Text />', () => {
  it('should render Text with default properties', () => {
    const displayText = 'Displaying some text';
    const { container, getByText } = render(
      <BladeProvider themeTokens={paymentTheme} colorScheme="light">
        <Text>{displayText}</Text>
      </BladeProvider>,
    );

    expect(getByText('Displaying some text')).toBeInTheDocument();
  });
});

Now this by default will run with jest-environment: jsdom. To test server-side rendering we can do something like this:

import { render } from '@testing-library/react';
import { renderToString } from 'react-dom/server';

describe('<Text />', () => {
  it('should render Text with default properties', () => {
    const displayText = 'Displaying some text';

    const renderOnServer = () => {
      return renderToString(
        <BladeProvider themeTokens={paymentTheme} colorScheme="light">
          <Text>{displayText}</Text>,
        </BladeProvider>,
      );
    };

    expect(renderOnServer).not.toThrow();

    const { getByText } = render(<div dangerouslySetInnerHTML={{ __html: renderOnServer() }} />);

    expect(getByText('Displaying some text')).toBeInTheDocument();
  });
});

the problem with the above approach is that even though we call the renderToString function it still won’t throw for cases where the components have used window or document because the jest environment is still jsdom and renderToString also works in browser environments so at this point of time jest doesn’t really understand that we intend to test the renderToString in server(precisely node) environment. More details in this issue

To overcome the above we might try removing the global window and document. The snippet for it will look something like below.

import { render } from '@testing-library/react';
import { renderToString } from 'react-dom/server';

describe('<Text />', () => {
  it('should render Text with default properties', () => {
    const displayText = 'Displaying some text';
	delete global.window;
	delete global.document;

    const renderOnServer = () => {
      return renderToString( // this fails because styled components and other dependents run with browser builds
        <BladeProvider themeTokens={paymentTheme} colorScheme="light">
          <Text>{displayText}</Text>,
        </BladeProvider>,
      );
    };

    expect(renderOnServer).not.toThrow();

    const { getByText } = render(<div dangerouslySetInnerHTML={{ __html: renderOnServer() }} />);

    expect(getByText('Displaying some text')).toBeInTheDocument();
  });
});

This feels like a hack and won’t work because the dependent libraries are still operating in browser mode(because jest is working with environment: jsdom and there are libs like styled-components and a lot others that have libs for diff environment under the same package name and the bundler resolves the package.json’s main or browser field based on the environment it runs in and over here even though we remove window and document the actual environment will still be browser so these libs will start failing.

So now what are the options at hand?

We can actually change the jest-environment: node to tell jest to run in node environment by doing following

/**
 * @jest-environment node
 */

import { renderToString } from 'react-dom/server';

describe('<Text />', () => {
  it('should render Text with default properties', () => {
    const displayText = 'Displaying some text';

    const renderOnServer = () => {
      return renderToString(
        <BladeProvider themeTokens={paymentTheme} colorScheme="light">
          <Text>{displayText}</Text>,
        </BladeProvider>,
      );
    };

    expect(renderOnServer).not.toThrow();
  });
});

now, this will work and mimic the actual node server environment. the only downside is now we can only test that the components are rendered statically and prevent unwanted usages of window and document instances in the server render phase. We can’t really use react-testing-library’s render method and query dom things since render from RTL is meant to run only in browser environments. There’s a comment regarding this from Kent

Also, with the above approach now we need a separate test file for testing ssr since the @jest-environment pragma works at file level and not test level

Proposal

I think for our use case what we can do is

  1. We can use @jest-environment node and assert that our components render successfully in node environment and prevent unwanted uses of window and document.
  2. For more functional instances we are anyways adding recipes for SSR so we can add components to the recipes that are more likely to be verified on the server.

Edit

Another approach suggested by @saurabhdaware here would work better. it’ll gives us best of both worlds

  1. We can use @jest-environment node and then after rendering the markup on server we can initialise jsdom and pass it the server-rendered markup to hydrate
  2. We can now query jsdom things as well

here’s how the test would look like

/**
 * @jest-environment node
 */
import { render } from '@testing-library/react';
import { renderToString } from 'react-dom/server';
import jsdom from 'jsdom';

describe('<Text />', () => {
  it('should render Text with default properties', () => {
    const displayText = 'Displaying some text';

    const App = (): ReactElement => (
      <BladeProvider themeTokens={paymentTheme} colorScheme="light">
        <Text>{displayText}</Text>,
      </BladeProvider>
    );

    const html = renderToString(
      <div id="root">
        <App />
      </div>,
    );

    const { JSDOM } = jsdom;
    const dom = new JSDOM(html);
    // @ts-expect-error ignoring this error since we are trying to make tests work between node and jsdom environments
    global.window = dom.window;
    global.document = dom.window.document;

    const { container, getByText } = render(<App />, {
      hydrate: true,
      container: document.getElementById('root') ?? undefined,
    });
    expect(container).toMatchSnapshot();
    expect(getByText(displayText)).toBeInTheDocument();
    expect(html).toMatchSnapshot();
  });
});
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Enable Server-Side Rendering for a React App
In this tutorial, you will initialize a React app using Create React App and then modify the project to enable server-side rendering.
Read more >
Server Side Rendering and its Relationship with SEO - Medium
Server -side rendering is the process of rendering a client-side JavaScript application to static HTML on the server. SSR is a large category ......
Read more >
SSR component testing · Issue #561 · testing-library/react ...
Currently there is no documentation, recommendations or API's that I could find regarding testing components that are rendered in a server side ......
Read more >
React Server Components - Patterns.dev
Today's Server-side rendering of client-side JavaScript can be suboptimal. JavaScript for your components is rendered on the server into an HTML string. This ......
Read more >
Rendering: Server and Client Components - Next.js beta docs
Server Components (RFC) allow developers to better leverage server infrastructure. For example, large dependencies that previously would impact the ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found