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
hi-ogawa added a commit to hi-ogawa/vite-plugins that referenced this pull request Jun 7, 2024
Hoping to implement this without runtime change (i.e. without ssg specific if-else branch). ~For now, it uses `(path)/index.data` as an output, so it'll probably require cdn to do rewrite from `(path)?__rsc` to `(path)/index.data`.~ 
(EDIT: stream request convention is changed to `(path)/__f.data`, so that prerender deployment is easier #361) 

- [x] react-router style api remix-run/react-router#11539
- [x] full ssg
- [x] dynamic route
- [ ] static deploy
  - [x] vite preview
  - [x] vercel
  - [ ] cloudflare
- [x] test

## future

- [ ] customize generation
  - [ ] `generateStaticParams` https://nextjs.org/docs/app/api-reference/functions/generate-static-params
  - [ ] auto crawling of `<a>`?
  - [ ] export `prerender` utility so that users can full control?
- [ ] output not-found
- [ ] preload flight stream
- [ ] tweak browser entry
  - [ ] ~automatically rewrite `(path)?__rsc` on browser~
    - requires #359
  - [ ] reduce unnecessary route node re-rendering
    - requires #353
- [ ] hybrid ssg and ssr
  - need manifest to rewrite to cdn?
- [ ] partial prerendering
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.

1 participant