Open-source project
artsy/fresnel avatar
artsy/fresnel

Fresnel renders every breakpoint and lets CSS hide the rest

An SSR compatible approach to CSS media query based responsive layouts for React.

1,298 stars66 forksTypeScriptNOASSERTION

At a glance

What is it?
@artsy/fresnel brings media query breakpoints into React as components, and its point of difference is server rendering: every breakpoint is emitted, wrapped in CSS that shows only the matching one, and a matchMedia call trims the DOM after hydration. That trades duplicate markup for a page that is correctly laid out before React boots.
Who is it for?
Adopt Fresnel if you server-render a React application and have noticed responsive components flashing or rendering empty before hydration. Do not adopt it if every component renders identical markup across breakpoints, since its main cost is duplicate server output for a problem you do not have.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 137 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem with matchMedia on a server

The usual React approach to responsive components is a render-prop over `matchMedia`, choosing one tree and returning it. On the client that works. On a server it cannot, and the README is direct about why.

It is impossible to reliably know the user's breakpoint during the server render phase, because that requires a browser. Sniffing the user agent does not fix it: device capabilities cannot be precisely matched to size, one mobile device may have greater pixel density than another, a device may span multiple breakpoints once orientation is considered, and on desktop there is no information at all.

The result is the familiar failure. Server-rendered HTML contains the wrong branch, or nothing useful, and the user sees a flash of the wrong layout while JavaScript loads and corrects it.

Fresnel's response is to stop choosing. `createMedia` takes a breakpoint map and returns components, and you write markup per breakpoint:

tsx
const { MediaContextProvider, Media } = createMedia({
  breakpoints: {
    sm: 0,
    md: 768,
    lg: 1024,
    xl: 1192,
  },
})

Each `Media` element accepts `at`, `greaterThan`, or `greaterThanOrEqual`, so `at="sm"` and `greaterThanOrEqual="lg"` are the two forms you actually use.

CSS does the selection, not JavaScript

The mechanism is the part worth understanding, because it explains both the benefit and the cost.

When server-rendering, all breakpoints get rendered by the server. Each `Media` component is wrapped by plain CSS that shows that breakpoint only if it matches the current browser size. The client can therefore start rendering the HTML and CSS while it receives the markup, long before React has booted. The README frames this as improved perceived performance.

The generated CSS is produced by `createMediaStyle()` and you inject it yourself:

tsx
export const mediaStyle = ExampleAppMedia.createMediaStyle()

and on the server you place it in a style tag in the head:

tsx
const html = ReactDOMServer.renderToString(<App />)
res.send(`
  <html>
    <head>
      <style type="text/css">${mediaStyle}</style>
    </head>
    <body>
      <div id="react">${html}</div>
      <script src='/assets/app.js'></script>
    </body>
  </html>
`)

Once client JavaScript boots and React attaches, it washes over the DOM and removes markup that is unneeded, via a `matchMedia` call.

The test procedure in the README is the best demonstration of the property: disable JavaScript, scale the browser window to a mobile size, reload. The mobile layout still renders correctly, with no user-agent hints involved.

The price is that the payload contains every breakpoint's markup, not one. For a page where mobile and desktop markup differ substantially, that is a real increase in HTML size, paid to remove a flash of wrong layout.

Installation and the React version split

Installation is one command, with a version branch for older React:

sh
# React 18+
yarn add @artsy/fresnel

# React 17
yarn add @artsy/fresnel@6

The split reflects that v8 targets React 18, with a peer dependency of `>=18.0.0`, and the v6 line exists for React 17. That is a sensible maintenance decision for a library that has been around long enough to have users on multiple React majors, though it does mean two lines have to be kept working.

The package publishes a compiled `dist` directory and type declarations, with `main` pointing at `dist/index.js` and typings at `dist/index.d.ts`, so consumers get types without a build step.

One thing to check before you copy from the README: the example code uses `ReactDOM.render`, which is the React 17 API, while the current peer dependency is React 18. The examples predate or lag the peer dependency bump, so treat the example snippets as illustration rather than something to paste into a React 18 application.

For framework integration the README says Fresnel works with Gatsby or Next.js's static hybrid approach to rendering, and points at a community package, gatsby-plugin-fresnel, for Gatsby configuration.

Four examples and what they cover

The `/examples` folder has four applications, and the README is clear about their roles.

The Basic example is the starting point. The Server-side Rendering example is the one that shows the actual mechanism, including the Media file that can be shared across the app, the App file as the launching point, the client mount and the server that injects the generated styles.

The Next example shows the framework integration. And the Kitchen Sink is described as the exhaustive deep-dive into the features, with the README saying Basic and SSR will get you one pretty far but the Kitchen Sink covers much more.

That ordering is a reasonable difficulty ramp, and having a kitchen sink in a library like this is a positive sign: it means the feature surface is documented by working code rather than by prose alone.

For the plugin ecosystem, the naming convention here is worth noting. This is a scoped package under the `@artsy` namespace with an `auto` release setup, a `.autorc` file and a `release` script of `auto shipit`, plus `renovate.json` for dependency updates. It is a maintained internal tool that happens to be published, and the release automation is visible in the repository rather than run by hand.

Tooling and maintenance signals

The build is Babel compiling `src` to `dist` with source maps, ignoring test directories, with `tsc --emitDeclarationOnly` for type declarations. Tests are run by `test`, which chains type checking, linting and Jest.

The linting choice is the notable one: `tslint -c tslint.json --project tsconfig.json`. The repository still uses TSLint and contains a root `tslint.json`. The available files do not establish how this choice compares with other projects or whether a migration has been considered.

The `engines` field is older still, requiring Node `>=12.20.2` and yarn `1.x.x`. That is a floor rather than a ceiling, so it does not block modern Node, but it does mean the project has not formally updated its stated support.

On releases, v8.5.0 shipped on 2026-02-06, after v8.4.0 in November 2025 and v8.3.0 the day before. The last push was on 2026-06-11 and the repository is not archived, so the recent push does not by itself indicate abandonment.

The licence is MIT in package.json, though GitHub's metadata does not classify it as standard, so the LICENSE file is the authoritative text.

Authors are listed as Eloy Durán and Art.sy Inc, and the project's rationale, from Artsy's engineering blog, is that responsive CSS is a server-rendering problem.

Editorial conclusion

Adopt Fresnel if you server-render a React application and have noticed responsive components flashing or rendering empty before hydration. Do not adopt it if every component renders identical markup across breakpoints, since its main cost is duplicate server output for a problem you do not have. Verify first that your breakpoints are the same ones your design system uses, that your React version satisfies the peer dependency of 18 or newer, and that you can accept a stylesheet injected into the document head on every route.

Frequently asked questions

What problem does @artsy/fresnel solve?

Media query based responsive layouts in React components when you server render. Because the server cannot know the user's breakpoint, Fresnel renders all breakpoints and lets CSS show only the matching one, removing the flash of the wrong layout before hydration.

Which version of @artsy/fresnel should I install?

The v8 line targets React 18 or newer, so `yarn add @artsy/fresnel`. For React 17 the README points at `@artsy/fresnel@6`, and the current peer dependency is react >=18.0.0.

Where do I put the generated media styles?

Create them with `createMediaStyle()` on the object returned by `createMedia`, then inject the result into a style tag in the document head on the server, as the SSR example does with express and renderToString.

How is Fresnel different from react-responsive?

Those libraries conditionally render one tree using matchMedia, which cannot work during a server render. Fresnel renders every breakpoint and hides the others with CSS, using matchMedia only after React attaches to clean up the DOM.

Official sources

  1. artsy/fresnel on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/artsy-fresnel.svg)](https://hysenlabs.com/projects/artsy-fresnel)

Was this useful?