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

Add support for prerendering #11539

Merged
merged 12 commits into from May 9, 2024
Merged

Add support for prerendering #11539

merged 12 commits into from May 9, 2024

Conversation

brophdawg11
Copy link
Contributor

@brophdawg11 brophdawg11 commented May 7, 2024

Add support for a prerender config that pre-renders HMTL and .data files for whatever URLs you specify. You can then deploy these files to a CDN, and load serve the HTML documents which hydrate into SPA's and load the .data files on client side navigations

export default defineConfig({
  plugins: [
    reactRouter({
      async prerender() {
        let slugs = await fakeGetSlugsFromCms();
        return ["/", "/about", ...slugs.map((slug) => `/product/${slug}`)];
      },
    }),
    tsconfigPaths(),
  ],
});

async function fakeGetSlugsFromCms() {
  await new Promise((r) => setTimeout(r, 1000));
  return ["shirt", "hat"];
}

Copy link

changeset-bot bot commented May 7, 2024

🦋 Changeset detected

Latest commit: fd6493a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
react-router Major
react-router-dom Major
@react-router/dev Major
@react-router/server-runtime Major
@react-router/node Major
@react-router/express Major
@react-router/serve Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines +82 to +92
setHeaders: function (res, path, stat) {
if (path.endsWith(".data")) {
res.set("Content-Type", "text/x-turbo");
} else {
// Cache as an immutable asset for 1 year
// Do this here instead of via the immutable/maxAge headers so we can
// conditionally apply it to assets (which are hashed), and not
// pre-rendered .data files (not hashed)
res.set("Cache-Control", "public, max-age=31536000, immutable");
}
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels potentially very bad to every put a 1 year immutable cache on a non-hashed .data URL, so this makes remix-serve by default not cache data files, and only apply the caching to other assets files.

page,
app,
"#child-error",
"Unable to decode turbo-stream response"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below, but we can't surface this underlying CDN error any longer with pre-rendering :/

Comment on lines +302 to 309
// Can't clone after consuming the body via turbo-stream so we can't
// include the body here. In an ideal world we'd look for a turbo-stream
// content type here, or even X-Remix-Response but then folks can't
// statically deploy their prerendered .data files to a CDN unless they can
// tell that CDN to add special headers to those certain files - which is a
// bit restrictive.
throw new Error("Unable to decode turbo-stream response");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why we can't surface the res.text() of CDN errors

Comment on lines +509 to +511
export const isSpaMode = ${
!ctx.reactRouterConfig.ssr && ctx.reactRouterConfig.prerender == null
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"SPA Mode" is specifically no SSR and only generating a root index.html - this isSpaMode flag is what tells the server.client not to SSR/hydrate beyond the root route.

Comment on lines +1683 to +1687
let { handler } = await getPrerenderBuildAndHandler(
viteConfig,
reactRouterConfig,
serverBuildDirectory
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPA Mode is effectively untouched, just sharing some code for creating the handler with the prerender code paths

Comment on lines 1743 to 1752
if (hasLoaders) {
await prerenderData(
handler,
reactRouterConfig.basename,
path,
clientBuildDirectory,
viteConfig,
requestInit
);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prerender .data files for any routes with at least one loader

@brophdawg11 brophdawg11 merged commit cb25a21 into v7 May 9, 2024
7 of 8 checks passed
@brophdawg11 brophdawg11 deleted the brophdawg11/prerender branch May 9, 2024 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant