In web applications, it’s fairly common to need to interact with HTTP primitives:
- You may want to set a custom HTTP status code on your HTML response, like a
404
code to represent a page where no resource was found. - You’ll often need to read from HTTP headers — especially the
Cookie
header, which gives you the cookies for this user — in order to authenticate or customize the application. - You may want to set additional HTTP headers on the HTML response, like a
Content-Security-Policy
header to control what sources the app can connect to, or aLocation
header to perform an HTTP redirect.
Quilt strongly encourages server rendering your application, so we want to make sure you have access to all these features of HTTP. Because Quilt is a component-focused framework, though, we put a component-friendly spin on these concepts. You can read HTTP details using hooks, and you can write them with hooks or components. This guide covers the prerequisites and limitations of these features, and how to use them in your application.
This guide assumes you are using the automatic server-side rendering provided by Quilt. This powerful feature creates a server runtime that will render your application and collect the “server side effects”, including using the HTTP utilities documented in this guide. If you are writing your own custom server-side rendering setup, or are using Quilt in a non-standard way, make sure you follow the guide on @quilted/react-http
, which covers how the server-side rendering of HTTP details works under the hood.
Note: In general, we recommend implementing redirects somewhere earlier in the network stack, before your application is rendered at all. While Quilt has good support for redirects, rendering your application is a slow way to discover a redirect. We only recommend using the features described below when the redirects are tied to the data you fetch for the rest of your application.
If you want to perform a redirect from one route in your application to another, return the Redirect
component:
import {Redirect} from '@quilted/quilt';
function MyComponent({shouldRedirect = false} = {}) {
if (shouldRedirect) {
return <Redirect to="/" />;
}
return <Ui />;
}
When you perform a redirect, Quilt will bail out of its server rendering process, set a 302
status code, and set the Location
header to the URL resolved from the to
prop. When a Redirect
is rendered on the client, it will perform a navigation with the router, replacing the current page in the history stack.
The to
prop on Redirect
works the same way as the Link
component. It can be an absolute path, which will be relative to the root of your app; a relative path (without a leading /
), which will be relative to the current URL; a URL
object; an object with optional path
, search
, and hash
keys; or a function that takes the current URL, and returns any of the above.
All of these redirects in the next example would go to /redirected
:
import {useRoutes, Redirect} from '@quilted/quilt';
export function App() {
return useRoutes([
{
match: '/',
render: ({url}) => {
return (
<Redirect to="/redirected" />
// <Redirect to="redirected" />
// <Redirect to={new URL('redirected', url)} />
// <Redirect to={{path: '/redirected'}} />
// <Redirect to={(currentUrl) => new URL('/redirected', currentUrl)} />
);
},
},
{match: 'redirected', render: () => <div>Redirected!</div>},
]);
}
As a convenience, if you are just redirecting one route to another, you can use the route’s redirect
key instead of rendering a Redirect
component:
import {useRoutes, Redirect} from '@quilted/quilt';
export function App() {
return useRoutes([
{
match: '/',
redirect: '/redirected',
// or redirect: 'redirected',
// or redirect: new URL('redirected', url),
// or redirect: {path: '/redirected'},
// or redirect: (currentUrl) => new URL('/redirected', currentUrl),
},
{match: 'redirected', render: () => <div>Redirected!</div>},
]);
}
When you render a Redirect
component, you can choose a status code other than the default 302
by passing a statusCode
prop:
import {Redirect} from '@quilted/quilt';
function MyComponent({shouldRedirect = false} = {}) {
if (shouldRedirect) {
return <Redirect to="/" statusCode={301} />;
}
return <Ui />;
}
You can set the status code on the response using the useResponseStatus
hook, or the ResponseStatus
component:
import {useResponseStatus, ResponseStatus} from '@quilted/quilt/http';
export function NotFoundUi() {
useResponseStatus(404);
// or...
return <ResponseStatus code={404} />;
}
Because setting a 404
status code is fairly common, there is a dedicated NotFound
component that is equivalent to the example above:
import {NotFound} from '@quilted/quilt/http';
export function NotFoundUi() {
return <NotFound />;
}
You can read cookies using the useCookie
hook:
import {useCookie, Redirect} from '@quilted/quilt';
import type {PropsWithChildren} from '@quilted/quilt';
export function GuardWithAuth({children}: PropsWithChildren) {
const authCookie = useCookie('Auth');
if (authCookie == null) {
return <Redirect to="/login" />;
}
return <>{children}</>;
}
On the server, these cookies are parsed from the Cookie
request header. On the client, these cookies are parsed from document.cookie
.
You can set an HTTP cookie by using the useResponseCookie
hook or ResponseCookie
component. Both accept the cookie name, value, and other cookie options.
import {useResponseCookie, ResponseCookie} from '@quilted/quilt/http';
export function StoreCurrentUser({user}: {user: string}) {
useResponseCookie('user', user, {path: '/profile'});
// or...
return <ResponseCookie name="user" value={user} path="/profile" />;
}
Similarly, if you want to set other response headers, you can use the ResponseHeader
component or useResponseHeader
hook:
import {useResponseHeader, ResponseHeader} from '@quilted/quilt/http';
export function Http() {
// FLoC off, Google.
// @see https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea
useResponseHeader('Permissions-Policy', 'interest-cohort=()');
// or...
return (
<ResponseHeader name="Permissions-Policy" value="interest-cohort=()" />
);
}
This library also provides dedicated components and hooks for a few common HTTP headers:
Cache-Control
withuseCacheControl
or<CacheControl />
Content-Security-Policy
withuseContentSecurityPolicy
or<ContentSecurityPolicy />
Cross-Origin-Embedder-Policy
withuseCrossOriginEmbedderPolicy
or<CrossOriginEmbedderPolicy />
Cross-Origin-Opener-Policy
withuseCrossOriginOpenerPolicy
or<CrossOriginOpenerPolicy />
Cross-Origin-Resource-Policy
withuseCrossOriginResourcePolicy
or<CrossOriginResourcePolicy />
Permissions-Policy
withusePermissionsPolicy
or<PermissionsPolicy />
Strict-Transport-Security
withuseStrictTransportSecurity
or<StrictTransportSecurity />
import {
useCacheControl,
useContentSecurityPolicy,
useCrossOriginEmbedderPolicy,
useCrossOriginOpenerPolicy,
useCrossOriginResourcePolicy,
usePermissionsPolicy,
useStrictTransportSecurity,
CacheControl,
ContentSecurityPolicy,
CrossOriginEmbedderPolicy,
CrossOriginOpenerPolicy,
CrossOriginResourcePolicy,
PermissionsPolicy,
StrictTransportSecurity,
} from '@quilted/quilt/http';
export function Http() {
useCacheControl({maxAge: 60, revalidate: true});
useContentSecurityPolicy({
defaultSources: ["'self'"],
frameAncestors: false,
upgradeInsecureRequests: true,
});
useCrossOriginEmbedderPolicy('require-corp');
useCrossOriginOpenerPolicy('same-origin');
useCrossOriginResourcePolicy('same-origin');
usePermissionsPolicy({interestCohort: false, geolocation: false});
useStrictTransportSecurity();
// or...
return (
<>
<CacheControl maxAge={60} revalidate />
<ContentSecurityPolicy
defaultSources={["'self'"]}
frameAncestors={false}
upgradeInsecureRequests
/>
<CrossOriginEmbedderPolicy value="require-corp" />
<CrossOriginOpenerPolicy value="same-origin" />
<CrossOriginResourcePolicy value="same-origin" />
<PermissionsPolicy interestCohort={false} geolocation={false} />
<StrictTransportSecurity />
</>
);
}
When using Quilt’s builds, all header-related components and hooks are entirely removed from the client-side bundle, because they do nothing there.
If you want to set cookies imperatively on the client-side, you can use the useCookies
hook to get a reference to the cookie store in the browser, and use its set
method to update the cookie on the client.
import {useCookies} from '@quilted/quilt';
export function SwitchUser({users}: {users: string[]}) {
const cookies = useCookies();
return (
<SelectButton
options={users}
onSelect={(user) => {
cookies.set('user', user, {path: '/profile'});
}}
>
Change user
</SelectButton>
);
}