| name | frontend |
| description | Frontend rules and React Router conventions. Use when working with frontend-related tasks. |
Frontend Rules
- The target audience is a general population - use simple user-friendly language and avoid technical terms
- Less is more: avoid unneccessary copy and explanations.
- The UI should be intuitive enough that it shouldn't be riddled with explainer text
- Avoid overused cliche AI language, terms and expressions.
- Use spacing to create a UI that breathes and is visually pleasant
- Use React Suspend with Async when possible to make the UI snappy
- Use optimistic updates when mutating data
- Avoid ternaries and the
boolean && <Component /> pattern inside components, use composition instead.
- Break down react components into several smaller components
- Prefer the
useLoaderData<typeof loader> hook in components over passing props
- Use the flat routes convention
kiliman/remix-flat-routes when organizing the UI
- Avoid using emojis, use the included icon library instead.
- Avoid content shift when possible
- Try to use skeletons and sensible placeholders when data is loading
- Also make sure pagination is smooth without navigational controls moving around
- Avoid using margin for your components; prefer flex gap and padding
- Use the react-router href helper when linking to internal pages for type safety, example:
<Link to={href("/products/:id", { id: "abc123" })} />
React Router
- The React Router app root is aliased as
#web/ for imports
- package.json:
"#web/*": "./resources/react_app/*.js"
- React Router v7+ (formerly Remix framework mode) now exposes framework APIs. Import framework code from
react-router.
json/defer are now deprecated, there's no need to wrap return objects/promises in these.
- Typing of actions and loaders are autogenerated and placed adjacent to the route with
./+types/<route-name>.js (done with parallel tsconfig rootDirs).
- Environment variables can be accessed from the context:
const env = await make('env')
- Always throw redirects and error responses to ensure proper type inference in loaders and actions:
- Errors should be caught and rethrown as Response with proper error code etc.
- Use inline mapping when returning DTOs from loaders/actions.
- The DTO should only contain the minimal number of fields that are needed by the UI components.
- Let TypeScript infer the types without explicit type annotations
- In auth routes, the current user is available on the http context auth property:
const user = http.auth.getUserOrFail()
- Do not add error handling for this; assume the user is authenticated through middleware
- The React Router code should only interact with services/db through the IoC container, and not have direct code dependencies on the AdonisJS code.
- Most work should be done in the service layer, avoid doing too much in actions and loaders.
- On the
context object you find the AdonisJS http context and the AdonisJS IoC container. This is the primary way the React Router app communicates with the backend.
- Services are added to the Adonis IoC container with string literals in #services/_index.ts, there you can see all available services.
- Use Locality of Behavior - for example keep route-specific components inside a given route folder instead of common components folder.
- Prefer small, focused components and functions
- Prefer composition over conditional rendering of react components
- Use inline types for react component props, for example
(props: { id: string })
- If a component is closely linked to loader data, infer the component type from the awaited loader return type
- Keep component structure in mind when creating loader DTO shapes
Form Data
- Use the react-router
useFetcher() hook and its fetcher.Form component when connecting to internal react-router routes
- Always implement the different states for the form pending/loading state
- Form submissions are validated using intent-validation.ts.
- The form should have a hidden input with name
intent which is the key for the intent validation, along with an object corresponding to the other input elements. The vine validator is then used like so:
const actionValidator = intentValidation({
login: {
email: vine.string().email().toLowerCase(),
},
})
const r = await http.request.validateUsing(actionValidator)
if (r.intent === 'login') {
}
Project Path Aliases
Important Path Aliases
#web/* - Points to ./resources/remix_app/* (defined in tsconfig.json)
#web/components - Used for shadcn components and utilities
- Example:
import { cn } from "#web/lib/utils"
React Router Flat Routes
- We use the flat-routes convention from
@kiliman/remix-flat-routes to organize our routes.
File Naming Conventions
We use the hybrid routes approach with the + suffix for directories:
- Route directories:
section+/ for directories that should be treated as flat files
- Example:
api+/endpoint.tsx → /api/endpoint
- Files within these directories use the parent directory name as prefix
api+/_index.tsx -> /api
- Directories without
+: _route.tsx becomes the route, for example api/_route.tsx -> /api
Practical Examples
- Nested route:
api+/endpoint.tsx → /api/endpoint
- Pathless nested folder:
_auth+/route.tsx -> /route
- Dynamic Parameters:
resources+/$resourceId.tsx → /resources/:resourceId
- Resource Routes:
thumbnails+/$videoId[.jpg].tsx → /thumbnails/:videoId.jpg
- Multiple Layout Nesting:
_auth+/admin+/settings.tsx → /admin/settings
- Layout:
endpoint+/_layout.tsx -> Creates a layout for any other paths inside the endpoint+ folder. Render children with <Outlet />
Best Practices
- Colocation: Keep related files together in directories with the
+ suffix
- Naming Consistency: Use descriptive names that reflect the route's purpose as it becomes part of the URL
- Pathless Layouts: Use
_ prefix for layout routes that shouldn't create URL segments
- Resource Files: Use square brackets
[] to denote routes that serve non-HTML resources
React Router Types
+types are autogenerated for routes by react-router and will always be relative to the current file and end with a .js extension: ./+types/<filename>.js.
- If the types appear to be missing, run
npx react-router typegen to regenerate them
- NEVER edit these types manually as they will be overridden
- If typegen fails with errors about route params, check your route file naming conventions
- Actions and Loader type signatures are typed using
import { Route } from './+types/<file_name>.js' and used like this:
export async function loader({ context }: Route.LoaderArgs) {
const { http, make } = context
const service = await make('my-service')
}
- The React Router autogenerated type signatures are located in
.react-router at the project root.
- The AdonisJS environment is augmented on these types in env.d.ts