Skip to content

Commit

Permalink
Fix server redirects to work properly with RSC responses
Browse files Browse the repository at this point in the history
Fixes #947
  • Loading branch information
blittle committed Mar 21, 2022
1 parent 45a8d6c commit 2799005
Show file tree
Hide file tree
Showing 8 changed files with 40 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useShopQuery, Seo, useRouteParams} from '@shopify/hydrogen';
import {Link, useShopQuery, Seo, useRouteParams} from '@shopify/hydrogen';
import gql from 'graphql-tag';

import ProductDetails from '../../components/ProductDetails.client';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export default function Redirect({response}) {
response.redirect('/products/snowboard');
return <div>This page is redirected</div>;
return response.redirect('/products/snowboard');
}
20 changes: 20 additions & 0 deletions packages/hydrogen/src/foundation/Redirect/Redirect.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {useEffect} from 'react';
import {useNavigate} from '../../client';

type RedirectProps = {
to: string;
};

export default function Redirect({to}: RedirectProps) {
const navigate = useNavigate();

useEffect(() => {
if (to.startsWith('http') || to.startsWith('https')) {
window.location.href = to;
} else {
navigate(to);
}
}, []);

return null;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {renderToString} from 'react-dom/server';
import {CacheSeconds, generateCacheControlHeader} from '../CachingStrategy';
import type {CachingStrategy} from '../../types';
import Redirect from '../../foundation/Redirect/Redirect.client';
import React from 'react';

export class ServerComponentResponse extends Response {
private wait = false;
Expand Down Expand Up @@ -55,6 +57,7 @@ export class ServerComponentResponse extends Response {

redirect(location: string, status = 307) {
this.writeHead({status, headers: {location}});
return React.createElement(Redirect, {to: location});
}

/**
Expand Down
10 changes: 4 additions & 6 deletions packages/hydrogen/src/framework/docs/pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,13 @@ export default function CustomPage({response}) {
#### `response.redirect()`

If you want to return users to a different URL, use `response.redirect()` in your server components.
If you want to return users to a different URL, use `response.redirect()` in your server components. Make sure to return

{% codeblock file %}

```jsx
export default function PageThatShouldRedirect({response}) {
response.redirect('https://yoursite.com/new-page');

return <p>Redirecting...</p>;
return response.redirect('/new-page');
}
```

Expand All @@ -145,7 +143,7 @@ The `redirect` function accepts a `location` URL and an optional `statusCode`, w
{% codeblock file %}

```jsx
response.redirect('https://yoursite.com/new-page', 301);
return response.redirect('https://yoursite.com/new-page', 301);
```

{% endcodeblock %}
Expand All @@ -154,7 +152,7 @@ response.redirect('https://yoursite.com/new-page', 301);
> This redirect method only supports initial server-rendered page responses. It does not yet support client-navigated responses.
> Caution:
> You must call `response.redirect()` before any calls to `useQuery` or `useShopQuery` to prevent streaming while the Suspense data is resolved, or use `response.doNotStream()` to prevent streaming altogether on the response.
> You must call `return response.redirect()` before any calls to `useQuery` or `useShopQuery` to prevent streaming while the Suspense data is resolved, or use `response.doNotStream()` to prevent streaming altogether on the response. The value must also be returned.
#### `response.send()`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export default function Index() {
<Link className="btn" to="/about">
About
</Link>
<br />
<Link className="redirect-btn" to="/redirected">
Redirect
</Link>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
export default function Redirected({response}) {
response.redirect('/about');

return (
<div>
<h1>This page has been moved to /about</h1>
</div>
);
return response.redirect('/about');
}
8 changes: 6 additions & 2 deletions packages/playground/server-components/tests/e2e-test-cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ export default async function testCases({
});

it('follows synchronous redirects', async () => {
await page.goto(getServerUrl() + '/redirected');
expect(await page.url()).toContain('/about');
await page.goto(getServerUrl() + '/');

await page.click('.redirect-btn');

await page.waitForURL('**/about');

expect(await page.textContent('h1')).toContain('About');
});

Expand Down

0 comments on commit 2799005

Please sign in to comment.