Introduction
This assumes all previous articles are read and understood.
This also assumes you are familiar with Next.js and Tailwind CSS.
Federation clarification
Section titled “Federation clarification”Although Karr can be a federated service, the frontend does not handle federation. All federation is done on server side, the client only communicates with its instance’s API.
The UI will however show which instance a user/trip comes from.
Structure
Section titled “Structure”All application code lives in the src/
directory.
Directorysrc/
Directoryapp/
Directory(index)/ Contains the home page files
- apage.tsx
Directorysearch/
Directory_components/ Local use components
- SearchBar.tsx
- SearchResults.tsx
- page.tsx
- …
Directoryassets/ All static assets to be imported
- …
Directorycomponents/ Global use components
- QueryProvider.tsx
Directoryutil/
- apifetch.ts Helper to fetch data from the API
- package.json
- next.config.js
- …
Conventions/Practices
Section titled “Conventions/Practices”Imports
Section titled “Imports”Only imports from the same directory or subdirectories are relative. Otherwise, use either:
@/*
: This alias’ root is atsrc/
, so@/i18n/routing
resolves tosrc/i18n/routing
.~/*
: This alias’ root is atsrc/app/
, so~/auth/actions
resolves tosrc/app/auth/actions
.
Components
Section titled “Components”Components should live as low in the file tree as possible.
- A component that is useful only in one page should live next to that page,
or in a
_components
directory next to that page. - A component that is used across multiple pages should live in the lowest common ancestor’s directory,
or its
_components
directory.
Highly reusable components such as buttons, tabs, avatar, stats graph, etc. should be defined in the @karr/ui
package.
More information on the package’s page.
Adding shadcn/ui components
Section titled “Adding shadcn/ui components”All shadcn/ui components are small and for targeted uses, so they should be added to the @karr/ui
package.
Please refer to this package’s documentation.
The only exception is for the pre-build Blocks and Charts — not the Chart component.
These are bigger pieces of UI that are composed of multiple different components,
so they should be directly put in apps/web
.
Refer to the previous section for details.
Images
Section titled “Images”Always use next/image
’s <Image>
,
importing the image directly into the tsx component when possible, along with placeholder="blur"
for a nice Blurhash while the image loads.
Fetching
Section titled “Fetching”Minimise as much as possible any dependence on external providers (Google Fonts, image cdn, etc.).
Always load files and content from API or assets/
.
From the API
Section titled “From the API”The API is built with Hono, which offers RPC for end-to-end type safety between the API and web front-end.
The RPC client is ready to use from @/util/apifetch
.
import { client } from "@/util/apifetch"
Then use it inside a query.
This way, the data
key will be properly typed with the return type from the API route.
For example:
const { data, isError, isLoading, error } = useQuery({ queryKey: ["user"], queryFn: async () => { const res = await client.user.info.$get() if (res.status !== 200) { throw new Error("Failed to fetch user data", { cause: await res.json() }) } return res.json() }})
This works the same for other request methods. The body fields will be properly typed.
For example with post:
const res = await tryCatch( client.trips.add.$post({ json: { ...data } }))if (!res.success) { console.error(res.error) toast.error("Something went wrong")} else { toast.success(t("added")) router.push("/trips/search")}
The trip fetch route gives back an SSE. This means trips are progressively returned.
This isn’t yet supported by Hono RPC, so use:
import { apiFetch } from "@/util/apifetch"
External
Section titled “External”You shouldn’t need to fetch from external urls, but if you do, use ofetch.
import { ofetch } from "ofetch"