Compare commits
2 commits
main
...
fd-redirec
Author | SHA1 | Date | |
---|---|---|---|
3fdcf6815b | |||
293a065ff7 |
|
@ -1,7 +1,7 @@
|
|||
import React, {Suspense, useState} from 'react';
|
||||
// @ts-ignore
|
||||
import {createRoot} from 'react-dom';
|
||||
import {BrowserRouter} from 'react-router-dom';
|
||||
import {BrowserRouter, Redirect} from 'react-router-dom';
|
||||
import type {ClientHandler} from './types';
|
||||
import {ErrorBoundary} from 'react-error-boundary';
|
||||
import {HelmetProvider} from 'react-helmet-async';
|
||||
|
@ -35,7 +35,8 @@ function Content({clientWrapper: ClientWrapper}: {clientWrapper: any}) {
|
|||
pathname: window.location.pathname,
|
||||
search: window.location.search,
|
||||
});
|
||||
const response = useServerResponse(serverState);
|
||||
|
||||
const response = useServerResponse(serverState).read();
|
||||
|
||||
return (
|
||||
<ServerStateProvider
|
||||
|
@ -46,8 +47,12 @@ function Content({clientWrapper: ClientWrapper}: {clientWrapper: any}) {
|
|||
<HelmetProvider>
|
||||
<BrowserRouter>
|
||||
<ServerStateRouter />
|
||||
{/* @ts-ignore */}
|
||||
<ClientWrapper>{response.read()}</ClientWrapper>
|
||||
{response.redirect ? (
|
||||
<Redirect to={response.redirect} />
|
||||
) : (
|
||||
/* @ts-ignore */
|
||||
<ClientWrapper>{response}</ClientWrapper>
|
||||
)}
|
||||
</BrowserRouter>
|
||||
</HelmetProvider>
|
||||
</QueryProvider>
|
||||
|
|
|
@ -20,6 +20,7 @@ import {ServerComponentResponse} from './framework/Hydration/ServerComponentResp
|
|||
import {ServerComponentRequest} from './framework/Hydration/ServerComponentRequest.server';
|
||||
import {dehydrate} from 'react-query/hydration';
|
||||
import {getCacheControlHeader} from './framework/cache';
|
||||
import type {ServerResponse} from 'http';
|
||||
|
||||
/**
|
||||
* react-dom/unstable-fizz provides different entrypoints based on runtime:
|
||||
|
@ -118,6 +119,14 @@ const renderHydrogen: ServerHandler = (App, hook) => {
|
|||
componentResponse.cacheControlHeader
|
||||
);
|
||||
|
||||
if (componentResponse.customHead) {
|
||||
writeHeadToServerResponse(componentResponse, response);
|
||||
if (response.statusCode >= 300 && response.statusCode < 400) {
|
||||
// Redirect
|
||||
return response.end();
|
||||
}
|
||||
}
|
||||
|
||||
if (!componentResponse.canStream()) return;
|
||||
|
||||
response.statusCode = didError ? 500 : 200;
|
||||
|
@ -205,6 +214,15 @@ const renderHydrogen: ServerHandler = (App, hook) => {
|
|||
* `template` and `script` tags inserted and rendered as part of the hydration response.
|
||||
*/
|
||||
onCompleteAll() {
|
||||
if (componentResponse.customHead) {
|
||||
writeHeadToServerResponse(componentResponse, response);
|
||||
if (response.statusCode >= 300 && response.statusCode < 400) {
|
||||
// Redirect: mock status to bypass fetch opaque responses
|
||||
response.statusCode = 299;
|
||||
return response.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Tell React to start writing to the writer
|
||||
startWriting();
|
||||
|
||||
|
@ -432,4 +450,23 @@ async function renderAppFromStringWithPrepass(
|
|||
: body;
|
||||
}
|
||||
|
||||
export function writeHeadToServerResponse(
|
||||
{customHead = {}}: ServerComponentResponse,
|
||||
serverResponse: ServerResponse
|
||||
) {
|
||||
if (customHead.headers) {
|
||||
for (const [key, value] of Object.entries(customHead.headers)) {
|
||||
serverResponse.setHeader(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (customHead.statusText) {
|
||||
serverResponse.statusMessage = customHead.statusText;
|
||||
}
|
||||
|
||||
if (customHead.status) {
|
||||
serverResponse.statusCode = customHead.status;
|
||||
}
|
||||
}
|
||||
|
||||
export default renderHydrogen;
|
||||
|
|
|
@ -35,10 +35,13 @@ function createFromFetch(fetchPromise: Promise<any>) {
|
|||
if (!response.ok) {
|
||||
throw new Error(`Hydration request failed: ${response.statusText}`);
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then((payload) => {
|
||||
return convertHydrationResponseToReactComponents(payload);
|
||||
|
||||
// Mocked status to bypass fetch opaque responses
|
||||
if (response.status === 299) {
|
||||
return {redirect: response.headers.get('location')};
|
||||
}
|
||||
|
||||
return response.text().then(convertHydrationResponseToReactComponents);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
|
|
|
@ -2,10 +2,18 @@ import {renderToString} from 'react-dom/server';
|
|||
import {CacheOptions} from '../../types';
|
||||
import {generateCacheControlHeader} from '../cache';
|
||||
|
||||
type Head = {
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
headers?: Record<string, any>;
|
||||
};
|
||||
|
||||
export class ServerComponentResponse extends Response {
|
||||
private wait = false;
|
||||
private cacheOptions?: CacheOptions;
|
||||
|
||||
public customHead: Head | undefined;
|
||||
|
||||
/**
|
||||
* Allow custom body to be a string or a Promise.
|
||||
*/
|
||||
|
@ -36,6 +44,26 @@ export class ServerComponentResponse extends Response {
|
|||
return generateCacheControlHeader(options);
|
||||
}
|
||||
|
||||
writeHead({status, statusText, headers}: Head = {}) {
|
||||
this.customHead = this.customHead || {};
|
||||
|
||||
if (status) {
|
||||
this.customHead.status = status;
|
||||
}
|
||||
|
||||
if (statusText) {
|
||||
this.customHead.statusText = statusText;
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
this.customHead.headers = {...this.customHead.headers, ...headers};
|
||||
}
|
||||
}
|
||||
|
||||
redirect(status: number, location: string) {
|
||||
this.writeHead({status, headers: {location}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the response from a Server Component. Renders React components to string,
|
||||
* and returns `null` to make React happy.
|
||||
|
|
Loading…
Reference in a new issue