Directory structure

Silgi reads these folders and files by name; everything else is yours. silgi init writes the ones it needs and you add the rest.

PathWhat it holds
app/pages/Pages. A file is a route: about.ts is /about, blog/[slug].ts is /blog/:slug, [[...path]].ts matches every path.
app/layouts/Layouts. default.ts wraps every page.
app/middleware/Route middleware that runs before a page renders.
app/plugins/App plugins, run in file name order. A .client.ts or .server.ts suffix limits a plugin to one side.
app/app.tsThe root component. It wraps every layout and page. The extension follows the library: app.tsx, app.vue, app.svelte. See Routing.
app/error.tsThe error page. Without it the library's default error page is used.
app/app.config.tsThe app config, merged over appConfig in the config. It is public.
app/env.tsdefineEnv({ … }): the environment variables the app reads, validated and typed. See Environment.
app/components/Components used without an import, with components: true. See Modules.
app/composables/, app/utils/Their exports are auto-imported in app files with imports.autoImport. See Modules.
app/graphql/The app's own GraphQL operations, *.graphql. See GraphQL.
server/index.tsThe door of the server. Its named exports are the handlers pages can call, and its default export, defineServer({ plugins, middleware, handlerMiddleware }), is the rest. See Server.
server/**Any other file under server/ is yours: handler definitions, helpers, anything. Folders mean nothing, and a file is reachable only through server/index.ts.
server/utils/Their exports are auto-imported in server files with imports.autoImport. See Modules.
server/graphql/The GraphQL schema (*.graphql), resolvers (*.resolver.ts) and context.ts, with server.graphql. See GraphQL.
shared/Code for both sides, imported through the #shared alias. Nothing in it is auto-imported.
public/Static files, copied into the build as they are, except dotfiles and dot-directories (.env, .git/), which are skipped with a warning; only public/.well-known/ is published. See Assets.
silgi.config.tsThe config.

.silgi/ holds build files and .output/ holds the build. Both are generated and belong in .gitignore.

Pages and layouts

A page and a layout with the html library:

import { Link } from "@silgi/html";
import type { HtmlProps } from "@silgi/html";

export default function DefaultLayout({ children }: HtmlProps): string {
  return `<div>
  <header>${Link({ to: "/", children: "Home" })} ${Link({ to: "/about", children: "About" })}</header>
  ${children}
</div>`;
}

A page that needs a value before it renders has a load file next to it, with the same name and a .load.ts ending. blog/[slug].load.ts loads the data for blog/[slug].ts. It runs on the server for the first render and in the browser for every navigation, and it calls handlers for anything that lives on the server. See Data.

import type { Load } from "silgi/app";

export const load = (({ params }) => ({ title: `Post ${params.slug}` })) satisfies Load;

Server

A handler is defineHandler({ … }) in any file under server/. What a page calls, and what a URL answers, is what server/index.ts exports. A handler with a route answers that URL; this one answers GET /api/hello:

import { defineHandler } from "silgi/server";

export const hello = defineHandler({
  route: "/api/hello",
  method: "GET",
  handler() {
    return { message: "Hello" };
  },
});

server/index.ts exports it with export { hello } from "./hello.ts";. A file name carries no path and no method; the definition says both. Server files are TypeScript: server/index.ts is .ts, and so are the files that define handlers.

Styles

Importing a .scss, .sass, .less or .styl file fails the build with app/main.scss: Sass needs a CSS preprocessor plugin; install @silgi/css and sass (06 §2) (Less and Stylus name their own tools), unless a module plugin compiles the file. Compile the file to CSS before the build and import the .css file, or add a module whose plugin compiles the extension.

bunx sass app/main.scss app/main.css

Assets

An image, font, audio, video or PDF file that app code imports becomes a hashed asset URL. import logo from "./logo.png" gives a string such as /_silgi/logo-49779094.png, the same in the server HTML and in the browser, under app.buildAssetsDir and app.baseURL. The build copies the file into .output/public/. Files in public/ are not imported: they are served as they are, at the path they have in the folder.