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

feat: Support React 18 preload-free-SSR like in NextJS #2253

Merged
merged 2 commits into from Nov 5, 2022
Merged

Conversation

ntucker
Copy link
Collaborator

@ntucker ntucker commented Nov 3, 2022

Fixes #2051 .

Motivation

Frameworks like https://github.com/ntucker/anansi have an extra data preload stage for SSR that improve efficiency; however React 18 doesn't require this, and in NextJS it just waterfalls everything. Additionally React SSR will only rerender components that suspended during this process which means all context-updates are non-existant.

Solution

The react team introduced useSyncExternalStore() in React 18 to deal with this specific case. So we can detect when it exists and we are SSR and use it instead of Context. We absolutely must keep context to keep React 16/17 compat.

Instructions will eventually live at https://resthooks.io/docs/guides/ssr

Server side

import express from 'express';
import { renderToPipeableStream } from 'react-dom/server';
import {
  createPersistedCacheProvder,
  createServerDataComponent,
} from '@rest-hooks/ssr';

const rootId = 'react-root';

const app = express();
app.get('/*', (req: any, res: any) => {
  const [ServerCacheProvider, useReadyCacheState, controller] =
    createPersistedCacheProvder();
  const ServerDataComponent = createServerDataComponent(useReadyCacheState);

  controller.fetch(NeededForPage, { id: 5 });

  const { pipe, abort } = renderToPipeableStream(
    <Document
      assets={assets}
      scripts={[<ServerDataComponent key="server-data" />]}
      rootId={rootId}
    >
      <ServerCacheProvider>{children}</ServerCacheProvider>
    </Document>,

    {
      onCompleteShell() {
        // If something errored before we started streaming, we set the error code appropriately.
        res.statusCode = didError ? 500 : 200;
        res.setHeader('Content-type', 'text/html');
        pipe(res);
      },
      onError(x: any) {
        didError = true;
        console.error(x);
        res.statusCode = 500;
        pipe(res);
      },
    },
  );
  // Abandon and switch to client rendering if enough time passes.
  // Try lowering this to see the client recover.
  setTimeout(abort, 1000);
});

app.listen(3000, () => {
  console.log(`Listening at ${PORT}...`);
});

Client

import { hydrateRoot } from 'react-dom';
import { getInitialData } from '@rest-hooks/ssr';

const rootId = 'react-root';

getInitialData().then(initialState => {
  hydrateRoot(
    document.getElementById(rootId),
    <CacheProvider initialState={initialState}>{children}</CacheProvider>,
  );
});

@ntucker ntucker requested a review from notwillk November 3, 2022 20:05
@ntucker ntucker changed the title Fix/next js feat: Support React 18 preload-free-SSR like in NextJS Nov 3, 2022
@ntucker ntucker changed the base branch from master to feat/controller-getstate November 3, 2022 20:10
@codecov-commenter
Copy link

codecov-commenter commented Nov 3, 2022

Codecov Report

Merging #2253 (bd9a0c4) into master (20aa9e4) will decrease coverage by 0.20%.
The diff coverage is 82.75%.

@@            Coverage Diff             @@
##           master    #2253      +/-   ##
==========================================
- Coverage   98.55%   98.34%   -0.21%     
==========================================
  Files         114      115       +1     
  Lines        1866     1878      +12     
  Branches      273      275       +2     
==========================================
+ Hits         1839     1847       +8     
- Misses         10       14       +4     
  Partials       17       17              
Impacted Files Coverage Δ
packages/core/src/react-integration/context.ts 100.00% <ø> (ø)
...re/src/react-integration/newhooks/useCacheState.ts 50.00% <50.00%> (ø)
packages/core/src/index.ts 100.00% <100.00%> (ø)
...ckages/core/src/react-integration/hooks/useMeta.ts 100.00% <100.00%> (ø)
...es/core/src/react-integration/newhooks/useCache.ts 100.00% <100.00%> (ø)
...ages/core/src/react-integration/newhooks/useDLE.ts 100.00% <100.00%> (ø)
...es/core/src/react-integration/newhooks/useError.ts 100.00% <100.00%> (ø)
...es/core/src/react-integration/newhooks/useFetch.ts 100.00% <100.00%> (ø)
...core/src/react-integration/newhooks/useSuspense.ts 100.00% <100.00%> (ø)
...act-integration/provider/ExternalCacheProvider.tsx 100.00% <100.00%> (ø)
... and 2 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@ntucker ntucker force-pushed the fix/next-js branch 2 times, most recently from 49b9d22 to 37a0dc5 Compare November 3, 2022 21:39
Base automatically changed from feat/controller-getstate to master November 3, 2022 22:41
@ntucker ntucker force-pushed the fix/next-js branch 2 times, most recently from bd9a0c4 to 4cfd7d0 Compare November 5, 2022 19:50
@ntucker ntucker merged commit 589030c into master Nov 5, 2022
@ntucker ntucker deleted the fix/next-js branch November 5, 2022 20:34
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

Successfully merging this pull request may close these issues.

useSuspense always displays fallback in next.js 12.1.6
3 participants