chore: Rework router structure#51
Conversation
|
I'm not sure how I feel about the remaining Also note that I think my buddy Claude removed/rewrote some code and or comments that I didn't instruct it to touch. Might have to do a second sweep if anyone feels like it was too rigourous. |
|
I think the overal idea of this pr implements our issue, but i do think keeping src/layouts or moving them to src/components/layouts, is a better idea, we didn't want the "-" notation in our file routes also seeing it this way makes me realize i didn't think it through where the page css should be located, if page logic lives in routes folder |
|
After some thinking, we can solve this in a few ways:
Any one of these would fix both the issue of duplicating style, and having There is of course also CSS-in-JS which would solve colocation in another way, but I'm not sure if that's an approach we'd like to explore. |
I think this will lead to another learning curve, imo if we choose to go a direction where we don't need separate css files we should move to tailwind v4 👀 |
|
Building on the options above — a fourth: push page-level styling into a Route files// src/routes/_app/index/route.tsx
import { createFileRoute } from "@tanstack/react-router";
import { Page, PageTitle } from "components/layout/page";
import { useTranslation } from "react-i18next";
export const Route = createFileRoute("/_app/")({
component: HomePage,
});
function HomePage() {
const { t } = useTranslation();
return (
<Page>
<PageTitle>{t("pages.home.title")}</PageTitle>
</Page>
);
}// src/routes/_auth/forgot-password/route.tsx
import { createFileRoute } from "@tanstack/react-router";
import { Page, PageTitle } from "components/layout/page";
import { useTranslation } from "react-i18next";
export const Route = createFileRoute("/_auth/forgot-password")({
component: ForgotPasswordPage,
});
function ForgotPasswordPage() {
const { t } = useTranslation();
return (
<Page align="center">
<PageTitle>{t("pages.forgotPassword.title")}</PageTitle>
</Page>
);
}No The component// src/components/layout/page/index.tsx
import { useDocumentTitle } from "@uidotdev/usehooks";
import { cva, type VariantProps } from "class-variance-authority";
import classNames from "classnames";
import { H1 } from "components/heading/heading";
import type { ComponentProps, ReactNode } from "react";
import style from "./page.module.scss";
const pageVariants = cva(style.page, {
variants: {
align: {
left: style.alignLeft,
center: style.alignCenter,
},
width: {
main: style.widthMain,
full: style.widthFull,
},
},
defaultVariants: {
align: "left",
width: "main",
},
});
type PageProps = ComponentProps<"div"> & VariantProps<typeof pageVariants>;
function Page({ align, width, className, ...rest }: PageProps) {
return (
<div
className={classNames(pageVariants({ align, width }), className)}
{...rest}
/>
);
}
function PageTitle({ children }: { children: string }) {
useDocumentTitle(children);
return <H1 size="medium">{children}</H1>;
}
function PageHeader({ children }: { children: ReactNode }) {
return <header className={style.header}>{children}</header>;
}
function PageSection({ children }: { children: ReactNode }) {
return <section className={style.section}>{children}</section>;
}
export { Page, PageTitle, PageHeader, PageSection };// src/components/layout/page/page.module.scss
.page {
display: flex;
flex-direction: column;
gap: var(--unit-8);
}
.widthMain { grid-column: main; }
.widthFull { grid-column: 1 / -1; }
.alignLeft { text-align: left; }
.alignCenter { text-align: center; }
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--unit-4);
}
.section {
display: flex;
flex-direction: column;
gap: var(--unit-4);
}Composes for richer pages without boolean propsfunction UsersPage() {
const { t } = useTranslation();
return (
<Page>
<PageHeader>
<PageTitle>{t("pages.users.title")}</PageTitle>
<Button>{t("pages.users.create")}</Button>
</PageHeader>
<PageSection>
<UserList />
</PageSection>
</Page>
);
}The full page markup lives in the route, just expressed in layout primitives instead of bespoke divs and SCSS. Why
Adds |
|
I thought about it a bit and played around with some options. Let me start with the basic approach of colocating. We said that we don't want This has the improvement that there are less
The second option is splitting the layout from the routes folder. We could just recreate the Wether we use compound layouts, or layout files; Pages will still need an exception sometimes. Sometimes only one or a couple of class names. Which will require a css module. Or sub components. Where do we put it? Let's have a live chat about this and cut the knot ✂️. Maybe have a tryout project where we do use option 1? |
| // TODO: THIS IS FAKE API HANDLING TO MAKE THE TEMPLATE | ||
| // WORK, PLEASE REMOVE THIS AND SET YOUR PROXY TO YOUR | ||
| // ACTUAL BACKEND. | ||
| client.interceptors.response.use(async (res) => { |
We exclude the |
|
Implemented the discussed changes. Changes
Route structure |
publicJorn
left a comment
There was a problem hiding this comment.
Looks very nice and clean! Some small adjustments and it's ready.
Closes #50
TLDR
Removed split between
src/routesandsrc/pages.What changed
Route structure: Dot-notation files (
_auth.login.tsxetc.) replaced with a proper nested folder structure.src/pagesremoved. Logic lives inroute.tsxfiles now.src/layoutsremoved. Inlined as-layout.tsx/-header.tsxunder their respective route folders.src/routerfolded intosrc/lib. Router setup is insrc/lib/router.tsx.getRouteApi(...)is gone.Cookie-based auth + React Query preload. Auth lives in the router.
_app/route.tsxusesqueryClient.ensureQueryData()inbeforeLoadto check the current user via cookie before any protected route renders. On failure, redirects to/loginwith the original path as aredirectparam. Mock API updated to support this.Supporting changes
.gitattributes: addedeol=lfto actually enforce LF on checkout (text=autoalone doesn't do it)biome.json: tightened ignore pattern from**/*.d.tsto**/*.module.scss.d.tsopenapi.jsonwith new / improved requests & responses.api.tsx->api.tsNo JSX in a non-component file. Fake token removed in favour of fake API response with real cookies.