Introduction
This assumes steps in Getting started and Project structure are done and understood.
This also assumes you are familiar with Next.js and Tailwind CSS.
Federation precision
Although Karr is 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.
However, the UI will show which instance a user/trip comes from.
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
Imports
Only imports from the same directory are relative, otherwise, all are @/*
.
This alias’ root is at src/
, so @/components/QueryProvider
resolves to src/components/QueryProvider
.
Components
Components that are meant to be used within only one scope should be defined in a local _components/
directory.
Components that are meant to be used in multiples pages should be defined in the root components/
directory.
To avoid a huge component dump, they can be sorted by use.
UI components such as buttons, tabs, avatar, stats graph, etc. should be defined in the @karr/ui
.
More information on the package’s page.
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
Always use next/image
’s <Image>
,
importing the image directly into the tsx component.
When possible, also use placeholder="blur"
for a nice Blurhash while the image loads.
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
To fetch data from the API, use @/util/apifetch
.
This predefines the behaviour and API base route for data fetching.
Trips
The trip fetch route gives back an SSE. This means trips are progressively returned.
The web frontend needs to use EventSource
.
Although won’t work for the moment, the API check auth with Authorization header, which is not supported by SSE/EventSource. Need to remove auth check on this route at least until mock login is built, then use cookies instead.
Working example in Svelte
<script lang="ts"> import { onMount } from "svelte";
let dataItems: Array<Record<string, any>> = []; let eventSource: EventSource | null = null;
let loading = true;
onMount(() => { eventSource = new EventSource("http://localhost:3000/v1/trip/search");
// Listen for the custom "new-trips" event eventSource.addEventListener("new-trip", (event) => { const data = JSON.parse(event.data); dataItems = [...dataItems, data]; console.log("New data received:", data); console.log("All data:", dataItems); });
eventSource.onerror = (error) => { if (eventSource?.readyState === 0) { console.warn("SSE connection closed by the server."); eventSource.close(); loading = false; } else { console.error("EventSource failed:", error); } };
return () => { // Cleanup when the component is destroyed eventSource?.close(); }; });</script>
<p> PoC won't work anymore, auth has been added to the API through Authorization header for the moment. Will move to cookie auth in the future to support EventSource.</p>
{#if dataItems.length === 0} <p>No data received yet.</p>{/if}
<div> {#each dataItems.sort((a, b) => parseInt(a.id) - parseInt(b.id)) as item} <div>{JSON.stringify(item)}</div> {/each} {#if loading} <p>Loading...</p> {/if}</div>
External
You shouldn’t need to fetch from external urls, but if you do, use ofetch.
import { ofetch } from "ofetch"