This is the official documentation website for the ReScript programming language. It is a fully pre-rendered static site (no server-side rendering at runtime) built with ReScript v12 + React 19 + React Router v7 + Vite 7 + Tailwind CSS v4, deployed to Cloudflare Pages.
- Node.js ≥ 22
- Yarn 4.12.0 (via Corepack)
app/ → React Router app shell (root layout, route definitions, route modules)
routes/ → Route components with loaders (e.g. BlogRoute.res, MdxRoute.res)
src/ → Core ReScript source code
bindings/ → Zero-cost bindings to JS libraries (@module externals)
common/ → Shared utilities, hooks, helpers (non-component modules)
components/ → Reusable React components
ffi/ → Plain JS interop files (prefer %raw over adding new files here)
layouts/ → Page layout components (DocsLayout, SidebarLayout, etc.)
markdown-pages/ → MDX content (blog posts, docs, community pages, syntax-lookup)
data/ → Hand-curated data (API docs JSON, sidebar ordering)
styles/ → CSS files (Tailwind v4 config in main.css, custom utilities)
scripts/ → Build/codegen scripts (ReScript + JS)
functions/ → Cloudflare Pages Functions (e.g. OG image generation)
compilers/ → Bundled ReScript compiler versions (for the playground)
plugins/ → HighlightJS & CodeMirror plugins
public/ → Static assets (images, fonts, favicons)
__tests__/ → Vitest browser-mode tests (Playwright)
| Command | Purpose |
|---|---|
yarn dev |
Run ReScript watcher + Vite dev server + Wrangler (parallel) |
yarn build |
Full production build (ReScript → scripts → Vite/React Router) |
yarn build:res |
ReScript compilation only |
yarn dev:res |
ReScript watch mode only |
yarn format |
Run Prettier + ReScript formatter |
yarn test |
Run example and href validation scripts |
yarn vitest |
Run Vitest browser tests with Playwright |
make |
Build (install deps + ReScript compile + update index) |
- Prefer small functions with a single purpose.
- Use a functional approach but don't make it too hardcore or difficult to understand.
- Keep files and modules small and focused.
.resfiles must always be capitalized (PascalCase), matching ReScript module conventions.- Use the pipe-first operator (
->) for chaining, which is idiomatic ReScript. - Resolve all warnings and treat them as errors. The project has
"error": "+8"inrescript.json. - Never directly edit an
.jsxfiles, you must edit the corresponding.resfile. The.jsxfiles are generated by the ReScript compiler and will be overwritten on the next compile.
- You use ReScript v12 (latest). Ensure all suggestions match this version.
- Ensure any produced JSX matches ReScript JSX v4 syntax (configured with
"preserve": true). - Never use the
BeltorJsmodules — these are legacy. Use the modernStdlib/ core modules instead. - Always use the
JSON.ttype for JSON values. - When dealing with promises, prefer
async/awaitsyntax. - The project opens
WebAPI.Globalglobally via compiler flags, so Web APIs are available without prefix. - Output format is ES modules with
.jsxsuffix, compiled in-source (.jsxfiles sit alongside.resfiles). - Reference the abridged documentation for clarification on how ReScript's APIs work: https://rescript-lang.org/llms/manual/llm-small.txt
- If you need more information you can access the full documentation, but do this only when needed as the docs are very large: https://rescript-lang.org/llms/manual/llm-full.txt
- Never use
%rawunless you are specifically asked to - Never use
Object.magic - Don't add type annotations unless necessary for clarity or to resolve an error. ReScript's type inference is powerful, and often explicit annotations are not needed.
@rescript/react— React bindings@rescript/webapi— Web API bindings (opened globally)
The project uses several patterns for JavaScript interop. Follow the existing conventions:
@moduleexternals for binding to npm packages (seesrc/bindings/for examples):@module("react-router") external useNavigate: unit => string => unit = "useNavigate"@modulewith@react.componentfor binding to external React components:@module("react-router") @react.component external make: (~children: React.element) => React.element = "Outlet"%raw()for inline JS when no clean binding is possible:let copyToClipboard: string => bool = %raw(`function(str) { ... }`)%%raw()(double percent) for top-level side-effectful JS imports.@scopefor binding to object properties likeprocess.env.external ... = "%identity"for zero-cost type coercions.- Put new bindings in
src/bindings/as a dedicated module (e.g.Fuse.res,DocSearch.res).
- This project uses React 19 and React Router v7 (framework mode).
- The site is pre-rendered (
ssr: false), so loaders have access to the filesystem during build. Loaders do not run on a server after the build. - Route modules live in
app/routes/and export aloaderand adefaultcomponent. - Route modules require both a
.resand a.resi(interface) file for Vite HMR to work. - Only a single React component can be exposed from a module's JS output.
Every route follows this pattern:
// SomeRoute.res
type loaderData = { ... }
let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
// filesystem access is available here (pre-render time only)
let data = ...
data
}
@react.component
let default = () => {
let data = ReactRouter.useLoaderData()
<SomeLayout> ... </SomeLayout>
}With a matching interface file:
// SomeRoute.resi
type loaderData = { ... }
let loader: ReactRouter.Loader.t<loaderData>
@react.component
let default: unit => React.elementReact.useStatetakes a function:let (age, setAge) = React.useState(_ => 4)- Every expression inside an interpolated string must be of type
string:- Wrong:
`age = ${42}` - Right:
`age = ${42->Int.toString}`
- Wrong:
typeis a keyword in ReScript. Usetype_for JSX props:<button type_="submit" />- You cannot add text as a direct child of a React component. Everything must be a
React.element:- Wrong:
<h1>Hello World</h1> - Right:
<h1>{React.string("Hello World")}</h1> - Use
React.string,React.int,React.float, andReact.arrayfor primitive conversions.
- Wrong:
- Use
React.nullfor rendering nothing. - Optional props use
?syntax:<button ?onClick>to passoption<handler>.
- Tailwind CSS v4 configured via the Vite plugin (
@tailwindcss/vite). There is notailwind.config.js. - All Tailwind configuration is in
styles/main.cssusing CSS-native@themeblocks. - LightningCSS is used as the CSS transformer.
- The project defines custom design tokens in
styles/main.css:- Custom colors:
gray-*,fire-*,sky-*,berry-*,water,turtle,orange-* - Custom font sizes:
text-11throughtext-68 - Custom utility classes:
hl-title,hl-1–hl-5,body-lg,body-md,body-sm,captions - Fonts: Inter (sans), Roboto Mono (mono)
- Custom colors:
- Use existing custom utilities and design tokens. Check
styles/main.cssbefore creating new ones.
-
Tests use Vitest 4 in browser mode with Playwright (Chromium).
-
Test files live in
__tests__/and are namedComponentName_.test.res(compiled to.test.jsx). -
Tests use custom ReScript bindings in
src/bindings/Vitest.res. -
Tests are visual/integration tests that render components and assert visibility, interactions, and screenshots.
-
Test pattern:
open Vitest test("description", async () => { await viewport(1440, 500) let screen = await render(<MyComponent />) let el = await screen->getByTestId("my-element") await element(el)->toBeVisible await element(el)->toMatchScreenshot("screenshot-name") })
-
Components requiring React Router context must be wrapped in
<MemoryRouter>. -
Do not use
<BrowserRouter>in tests. -
Run tests with
yarn vitest --browser.headless --run. -
Do not update snapshots without asking for confirmation.
- Documentation content is in
markdown-pages/organized by section (blog, docs, community, syntax-lookup). - MDX is processed by
react-router-mdxwith remark/rehype plugins. - Custom MDX components are mapped in
app/routes/MdxRoute.res(e.g.<Info>,<Warn>,<CodeTab>,<Video>). - Code examples in markdown use
```res example(runnable),```res sig(signature), and```res prelude(shared context).
- Prettier with the
@prettier/plugin-oxcparser for JS formatting. - ReScript formatter (
rescript format) for.resfiles. - Lefthook runs
yarn formaton pre-commit (auto-stages fixed files). - Generated
.mjs/.jsxoutput files from ReScript are git-tracked but excluded from Prettier.
- Do not modify generated
.jsx/.mjsfiles directly — they are ReScript compiler output. - Do not use
@genType— the project does not use it. - The
src/ffi/directory is legacy; prefer%rawstatements for new JS interop. - The README references some outdated structures (Next.js,
pages/directory) — ignore those references. The project has migrated to React Router v7. - When editing route files, always update both the
.resand.resifiles.