Let's take a Next.js example application that passes data from the server to the client-side through props. You can find my example in the repo bahmutov/next-state-overwrite-example. Here is the home page pages/index.js:
1 | import Head from 'next/head' |
The getServerSideProps
executes server-side, we can see the console log message in the terminal
1 | wait - compiling... |
The data is then passed to the client-side through prop
to the component where it is used to render the text
1 | <p className={styles.description} data-cy="greeting"> |
We can see the result in the browser
The default test
We can write a test to confirm the expected message appears on the page.
1 | /// <reference types="cypress" /> |
The test is green
Using the NEXT_DATA
We can avoid hard-coding the expected text. Instead let's grab the server-side greeting from the page itself. If we look at the source code for the page, we can find the <script id="__NEXT_DATA__" type="application/json">
element with the server-side props.
The Next.js code takes that script and parses it into an object window.__NEXT_DATA__
.
We can access this object from the test to get the expected text.
1 | it('shows the text from the __NEXT_DATA__', () => { |
Modifying the NEXT_DATA
Let's overwrite the NEXT_DATA before the application uses it. We can do it in two ways.
Change the NEXT_DATA object when set
We can intercept the moment when the framework parses the <script id="__NEXT_DATA__" type="application/json">
and sets the window.__NEXT_DATA__
property. I will use the Object.defineProperty(win, '__NEXT_DATA__' ...)
with set
and get
handlers. The set
handler will be called when the framework sets the object. The test can replace the property in the object and the application happily continues from this point.
1 | it('removes the text from __NEXT_DATA__', () => { |
Well, not 100% happily. If we used the prop to set the component's state, it would be enough. But we are using the greeting server-side to render the initial HTML. If we replace just the property value, the server-side HTML and the client-side HTML versions won't match. Which is what the React complains about in the DevTools:
In this case, we need to replace both the page prop and "fix" the server-side HTML the browser receives.
Replace HTML
Let's "fix up" the HTML sent by the server before we replace the greeting inside the NEXT_DATA. Here I put an arrow on the string we need to replace to avoid the warning:
We can use the cy.intercept command for this.
1 | it('patches HTML and removes the text from __NEXT_DATA__', () => { |
No more React warnings.
It is not enough to modify the __NEXT_DATA__
on the very first page visit. The application might navigate to other pages. For example, let's add "About" page.
1 | <Link href="/about"> |
The About pages will have its own server-side props
1 | import Head from 'next/head' |
The Next.js framework sends these server-side props through a JSON fetch request to http://localhost:3000/_next/data/development/about.json
endpoint.
We can spy on this request using cy.intercept command. Important: the request comes with the header If-None-Match
which controls the caching. In most cases, the server-side JSON object is the same, thus the Next server responds with 304 without data. We need to intercept the actual object during the test, thus we will remove this header to force the server to send the full object. For more examples of cy.intercept
command read Cypress cy.intercept Problems.
Let's intercept and modify the server-side props for the About page.
1 | it('modifies __NEXT_DATA__ on navigation', () => { |
Note: if we go back to the Home page, the framework fetches its page props the same way using the GET development/index.json
request.