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 server redirects #491

Merged
merged 2 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}>
molefrog marked this conversation as resolved.
Show resolved Hide resolved
<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();
Copy link
Owner

Choose a reason for hiding this comment

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

There is an internal helper hook useLocationFromRouter that you can use combined with useRouter() to avoid extra context calls.

Copy link
Author

Choose a reason for hiding this comment

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

Not sure I understand this suggestion, there's no need to use location (the result of useLocationFromRouter) in this instance.

Copy link
Author

Choose a reason for hiding this comment

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

oh never-mind, I see. I can optimize the existing call to useLocation above since I have a router ref. I pushed the changes.


// 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;
}
molefrog marked this conversation as resolved.
Show resolved Hide resolved

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;
};
Loading