One is entirely configured through a Vite plugin:
vite.config.ts
Note that some of this configuration is ultimately stored into environment variables automatically, accessible in your apps and website.
The native object allows you to configure your native apps.
One will set up your React Native app to run via AppRegistry.registerComponent(app.key)
.
This setting must match the key you gave the React Native app container you've built.
The web object allows you to configure web specific settings.
Choose between one of ssg
, spa
or ssr
. The default is ssg
.
Each mode has trade-offs:
ssg
stands for "Static Site Generated", it is the default mode.SSG is a great general purpose strategy, where your pages are rendered fully at build-time and turned into static HTML. This means you can get a "perfect" first load, where your app is fully rendered with all content, servable easily by a CDN.
The JS is sent alongside it, and React will hydrate your app and resume running from there. The downside is that you do have some complexity in needing to ensure your pages can render in node, the upside is increased performance and SEO on the web.
spa
stands for "Single Page App", which is the simplest strategy.SPA mode will not render the page on the server at all, and instead only serve the JavaScript to render your page to the users browser. This mode avoids some complexity - you don't have to make your React code
ssr
stands for "Sever Side Rendered".This mode will render your page on the server at the time the browser requests it, running loaders as well for each request.
This mode is likely the least useful, as it means that you must make a full trip to your database at the time of each request, and your client is left waiting for the full render from the server before it can show anything. That said, this mode can be useful if you have dynamically generated content but still want to have the best possible SEO, at the cost of some complexity in supporting server rendering.
You can apply server-level redirects. One serves your app with Hono in production, and so the redirect pattern is simple: your source
will be passed to Hono.app.get(source)
. Any matching path segments that start with :
will be replaced into the matching segments in destination
. And the redirect will send a response type of 301
if permanent is true, otherwise it will be 302
.
Enables the new React Compiler for both native and web. Will slow down compilation as it uses Babel, but will improve app performance and reduce the need to write memoization.
Enables React Scan. Helps you re-rendering and other performance issues. We disabled native support for now as it was running too slow, follow this PR for their status on getting native to stability (if you override the react-scan version with one from that PR, it should work on native).
While building One we found ourselves needing to apply small patches to a variety of packages. Packages in the React Native ecosystem are often published only for Metro, and so have a variety of weird setups.
We built an internal Vite plugin to easily apply patches across any package manager, but after installing One into a few larger production projects, we found ourselves wanting to have access to it.
The deps
option is a powerful way to coerce your node_modules to be compatible with Vite.
Here's an example:
vite.config.ts
Any transforms that are applied to specific files in node_modules will only be applied once. The original files will be stored alongside the transformed ones.
One runs on Vite 6 and makes use of their new Environment API.
This makes it easier to target React Native, as you can specify configuration for iOS, Android and web separately. One defines ios
and android
environments, respectively. For web, we follow the Vite standard of having a client
and ssr
environments (for client-side and server-side bundles).
You'll want to lean on using the Environment API to configure as much as you can in your vite.config.ts
. It may also mean that some plugins can cause issues by configuring things across all four environments when you really only want it for client-side, or not. We're working on some helpers to make this easier, but in general you can check for this.environment.name
in plugin hooks (like transform
and load
) to conditionally exit early from their logic for environments where you don't want the plugin logic to run.
One assumes the following environments:
client
: Web client-side (matching Vite default)ssr
: Web server-side (matching Vite default)ios
: iOSandroid
: AndroidWe set up platform-specific extensions based on the environment:
.web.(js|ts|tsx|mjs)
.(ios|native).(js|ts|tsx|mjs)
.(android|native).(js|ts|tsx|mjs)
The one/vite
import has a few other exports that may be useful.
resolvePath
When you are setting an alias
in Vite, it wants you to fully specify the import path. We use resolvePath
to help with this, it's a bit like require.resolve
that works in ESM or CJS. It's also similar to import.meta.resolve
, except it returns an absolute path that Vite expects rather than a file path.
One sets up some default configuration out of the box, including a variety of plugins and settings that make React Native and the ecosystem of packages that are popular "just work".
Some base configuration we set:
publicDir
to "public"
clearScreen
to false
so we can output the quick actionsWe set up defines
:
__DEV__
to true in development mode, or false otherwiseprocess.env.NODE_ENV
We set up alias
based on environment:
react-native
=> react-native-web
for web environmentsWe also detect if you have a PostCSS config file (js, ts, or json) and set the css
. The internal logic is something like:
Edit this page on GitHub.