Documentation

Routing

Loly uses file-based routing. Routes are automatically created from your file structure.

Routes are automatically created from your file structure:

File PathRoute
app/page.tsx/
app/about/page.tsx/about
app/blog/[slug]/page.tsx/blog/:slug
app/post/[...path]/page.tsx/post/* (catch-all)

Dynamic Routes

Create dynamic routes using square brackets:

app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
  return <h1>Post: {params.slug}</h1>;
}

Route parameters are passed directly as props to your component.

Catch-All Routes

Use three dots to create catch-all routes:

app/post/[...path]/page.tsx
export default function Post({ params }: { params: { path: string[] } }) {
  return <h1>Path: {params.path.join("/")}</h1>;
}

Nested Layouts

Create nested layouts by adding a layout.tsx file:

app/blog/layout.tsx
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <aside>Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

Important: Layouts should NOT include <html> or <body> tags. The framework automatically handles the base HTML structure.

Layout Server Hooks

Layouts can have their own server hooks that provide stable data shared across all pages. Create layout.server.hook.ts in the same directory as layout.tsx:

app/layout.server.hook.ts
import type { ServerLoader } from "@lolyjs/core";

export const getServerSideProps: ServerLoader = async (ctx) => {
  // Stable data shared across all pages
  return {
    props: {
      appName: "My App",
      navigation: [
        { href: "/", label: "Home" },
        { href: "/about", label: "About" },
        { href: "/blog", label: "Blog" },
      ],
    },
    metadata: {
      // Base metadata for all pages
      description: "My App - Description",
      openGraph: {
        siteName: "My App",
        type: "website",
      },
    },
  };
};

Props Combination:

  • Layout props (from layout.server.hook.ts) are stable and available in both the layout and all pages
  • Page props (from page.server.hook.ts) are specific to each page and override layout props if there's a conflict
  • Combined props are available in both layouts and pages
app/layout.tsx
export default function RootLayout({ children, appName, navigation, user }) {
  // appName and navigation come from layout.server.hook.ts
  // user can come from page.server.hook.ts (if it exists)
  return (
    <div>
      <nav>
        <h1>{appName}</h1>
        {navigation.map(item => (
          <Link key={item.href} href={item.href}>{item.label}</Link>
        ))}
      </nav>
      {children}
    </div>
  );
}

Metadata Combination: Metadata is also combined intelligently:

  • Layout metadata acts as base/fallback
  • Page metadata overrides specific fields
  • Nested objects (openGraph, twitter) are merged shallowly

Route Middleware

Unique Loly feature: You can define middlewares directly in your routes using beforeServerData in page.server.hook.ts:

app/admin/page.server.hook.ts
import type { RouteMiddleware, ServerLoader } from "@lolyjs/core";

export const beforeServerData: RouteMiddleware[] = [
  async (ctx, next) => {
    // Verify authentication
    const token = ctx.req.headers.authorization;
    if (!token) {
      ctx.res.status(401).json({ error: "Unauthorized" });
      return;
    }
    
    const user = await verifyToken(token);
    ctx.locals.user = user;
    await next();
  },
];

export const getServerSideProps: ServerLoader = async (ctx) => {
  return {
    props: {
      user: ctx.locals.user,
    },
  };
};

This separation allows you to keep server logic separate from React components, making testing and code organization easier.