An SSR compatible approach to CSS media query based responsive layouts for React.
npm install @artsy/fresnel[![CircleCI][ci-icon]][ci] [![npm version][npm-icon]][npm]
> The Fresnel equations describe the reflection of light when incident on an
> interface between different optical media.
– https://en.wikipedia.org/wiki/Fresnel_equations
``sh
# React 18+
yarn add @artsy/fresnel
# React 17
yarn add @artsy/fresnel@6
`
Table of Contents
- Overview
- Basic Example
- Server-side Rendering (SSR)
- Usage with Next
- Example Apps
- Why not conditionally render?
- API
- Pros vs Cons
- Development
When writing responsive components it's common to use media queries to adjust
the display when certain conditions are met. Historically this has taken place
directly in CSS/HTML:
`css`
@media screen and (max-width: 767px) {
.my-container {
width: 100%;
}
}
@media screen and (min-width: 768px) {
.my-container {
width: 50%;
}
}
`html`
By hooking into a breakpoint definition, @artsy/fresnel takes this declarative
approach and brings it into the React world.
`tsx
import React from "react"
import ReactDOM from "react-dom"
import { createMedia } from "@artsy/fresnel"
const { MediaContextProvider, Media } = createMedia({
// breakpoints values can be either strings or integers
breakpoints: {
sm: 0,
md: 768,
lg: 1024,
xl: 1192,
},
})
const App = () => (
)
ReactDOM.render(
`
The first important thing to note is that when server-rendering with
@artsy/fresnel, all breakpoints get rendered by the server. Each Media
component is wrapped by plain CSS that will only show that breakpoint if it
matches the user's current browser size. This means that the client can
accurately start rendering the HTML/CSS while it receives the markup, which is
long before the React application has booted. This improves perceived
performance for end-users.
Why not just render the one that the current device needs? We can't accurately
identify which breakpoint your device needs on the server. We could use a
library to sniff the browser user-agent, but those aren't always accurate, and
they wouldn't give us all the information we need to know when we are
server-rendering. Once client-side JS boots and React attaches, it simply washes
over the DOM and removes markup that is unneeded, via a matchMedia call.
First, configure @artsy/fresnel in a Media file that can be shared across
the app:
`tsx
// Media.tsx
import { createMedia } from "@artsy/fresnel"
const ExampleAppMedia = createMedia({
breakpoints: {
sm: 0,
md: 768,
lg: 1024,
xl: 1192,
},
})
// Generate CSS to be injected into the head
export const mediaStyle = ExampleAppMedia.createMediaStyle()
export const { Media, MediaContextProvider } = ExampleAppMedia
`
Create a new App file which will be the launching point for our application:
`tsx
// App.tsx
import React from "react"
import { Media, MediaContextProvider } from "./Media"
export const App = () => {
return (
)
}
`
Mount on the client:
`tsx
// client.tsx
import React from "react"
import ReactDOM from "react-dom"
import { App } from "./App"
ReactDOM.render(
`
Then on the server, setup SSR rendering and pass mediaStyle into a