Skip to content

Commit

Permalink
feat: support server redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
Gary Bettencourt committed Oct 23, 2024
1 parent 48b63a9 commit 0f529cb
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 2 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -818,13 +818,20 @@ import { Router } from "wouter";

const handleRequest = (req, res) => {
// top-level Router is mandatory in SSR mode
// pass an optional context object to handle redirects on the server
const ssrContext = {};
const prerendered = renderToString(
<Router ssrPath={req.path} ssrSearch={req.search}>
<Router ssrPath={req.path} ssrSearch={req.search} ssrContext={ssrContext}>
<App />
</Router>
);

// respond with prerendered html
if (ssrContext.redirectTo) {
// encountered redirect
res.redirect(ssrContext.redirectTo);
} else {
// respond with prerendered html
}
};
```

Expand Down
7 changes: 7 additions & 0 deletions packages/wouter/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const defaultRouter = {
// this option is used to override the current location during SSR
ssrPath: undefined,
ssrSearch: undefined,
// optional context to track render state during SSR
ssrContext: undefined,
// customizes how `href` props are transformed for <Link />
hrefs: (x) => x,
};
Expand Down Expand Up @@ -319,11 +321,16 @@ export const Redirect = (props) => {
const { to, href = to } = props;
const [, navigate] = useLocation();
const redirect = useEvent(() => navigate(to || href, props));
const { ssrContext } = useRouter();

// redirect is guaranteed to be stable since it is returned from useEvent
useIsomorphicLayoutEffect(() => {
redirect();
}, []); // eslint-disable-line react-hooks/exhaustive-deps

if (ssrContext) {
ssrContext.redirectTo = to;
}

return null;
};
15 changes: 15 additions & 0 deletions packages/wouter/test/ssr.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Redirect,
useSearch,
useLocation,
SsrContext,
} from "wouter";

describe("server-side rendering", () => {
Expand Down Expand Up @@ -73,6 +74,20 @@ describe("server-side rendering", () => {
expect(rendered).toBe("");
});

it("update ssr context", () => {
const context: SsrContext = {};
const App = () => (
<Router ssrPath="/" ssrContext={context}>
<Route path="/">
<Redirect to="/foo" />
</Route>
</Router>
);

renderToStaticMarkup(<App />);
expect(context.redirectTo).toBe("/foo");
});

describe("rendering with given search string", () => {
it("is empty when not specified", () => {
const PrintSearch = () => <>{useSearch()}</>;
Expand Down
7 changes: 7 additions & 0 deletions packages/wouter/types/router.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export interface RouterObject {
readonly hrefs: HrefsFormatter;
}

// state captured during SSR render
export type SsrContext = {
// if a redirect was encountered, this will be populated with the path
redirectTo?: Path;
};

// basic options to construct a router
export type RouterOptions = {
hook?: BaseLocationHook;
Expand All @@ -32,5 +38,6 @@ export type RouterOptions = {
parser?: Parser;
ssrPath?: Path;
ssrSearch?: SearchString;
ssrContext?: SsrContext;
hrefs?: HrefsFormatter;
};

0 comments on commit 0f529cb

Please sign in to comment.