Migrating from Nuxt

Silgi has the shape of Nuxt: file-based pages, layouts, route middleware, a server/ folder, useFetch, useState, route rules, modules and layers. The names are often the same. Four things are different from the start:

  • You pick the UI library. Nuxt is Vue. Silgi runs React, Vue, Preact, Solid, Svelte or plain HTML, so a Nuxt app moves most easily to @silgi/vue. Hooks return the library's own values, such as a Ref in Vue; see Adapters.
  • The server is Web standards. There is no h3 or Nitro. A handler gets a SilgiEvent that holds a Web Request, and returns a value or a Response.
  • Auto-imports are off. Import from silgi/app, silgi/server and @silgi/<library>, or turn on imports.autoImport.
  • Bun builds it. build is Bun.build options, not Vite or webpack. Vite plugins and Nuxt modules do not run.

The tables list a Nuxt name, what to use in Silgi, and where they differ. none means Silgi has no equivalent today. Coming from another framework? See Next, Remix and React Router or SvelteKit.

Folders and files

NuxtSilgiNotes
nuxt.config.tssilgi.config.tsdefineConfig; keys below
app/app/the same name; srcDir
app/pages/app/pages/same file syntax, with the extension of your library
app/layouts/app/layouts/default wraps every page; a layout receives the page as children (Vue: the default slot)
app/middleware/app/middleware/*.global.ts, .client.ts and .server.ts work as in Nuxt
app/plugins/app/plugins/file name order; .client.ts and .server.ts work
app/app.vueapp/app.<ext>optional root component around every layout; it renders Outlet
app/error.vueapp/error.<ext>rendered without a layout
app/app.config.tsapp/app.config.tsdefineAppConfig
app/components/app/components/with components: true; the name is the path in PascalCase, as in Nuxt
app/composables/, app/utils/the sameauto-imported only with imports.autoImport
server/api/, server/routes/server/index.tsa route is a handler with a route and a method: defineHandler({ route: "/api/posts/:id", method: "GET", … }), exported from the door. A file name carries no path and no method
server/middleware/, server/plugins/middleware and plugins of defineServer in server/index.tsthe order written, not file name order
server/utils/server/utils/auto-imported only with imports.autoImport
public/public/
layers (extends)extends
shared/an alias #shared to shared/no auto-imports
.nuxt/.silgi/generated
.output/.output/
.env, .env.<envName>.env in the app root is read by default; --mode <name> reads .env.<name> and .env.<name>.local; --dotenv <file> on dev, build, generate and preview adds another fileset variables in the environment in production. See Environment
app/assets/noneimport files from your components
modules/nonelist local modules in modules
content/none
server/tasks/server/tasks/the same folder; the file shape differs, see Tasks, database, blob and storage
server/database/migrations/ (NuxtHub)server/db/migrations/with @silgi/db; a SQL file named <number>_<name>.sql
.nuxtignore, .nuxtrcnonea file or folder that starts with _ or . is ignored in pages/

The path aliases are ~ and @ for app/, and #shared. There is no ~~ or #imports.

A page that reads its data with a handler:

import { useAsyncData, useHead, useRoute } from "@silgi/react";
import type { PageMeta } from "silgi/app";
import { getPost } from "#server";

export const meta = { layout: "blog", middleware: "auth" } satisfies PageMeta;

export default function Post() {
  const { slug } = useRoute().params;
  const { data } = useAsyncData(getPost, () => ({ id: slug as string }));
  useHead({ title: data?.title ?? "Post" });
  return <h1>{data?.title}</h1>;
}

Pages and routing

The file syntax is Nuxt's: [slug], [[slug]], [...slug], (group) folders and parent.vue next to a parent/ folder. Silgi adds [[...path]] for an optional catch-all. See Routing.

NuxtSilgi
definePageMeta({ layout, middleware })export const meta = { layout, middleware } satisfies PageMeta, a plain object that Silgi reads when the page loads; PageMeta comes from silgi/app. middleware takes one name ("auth"), a function or an array of them, as in Nuxt. In Vue it goes in a plain <script lang="ts"> block, in Svelte in <script module lang="ts">.
declare module "#app" { interface PageMeta { title?: string } }declare module "silgi/app" { interface SilgiPageMeta { title?: string } }; PageMeta is a type and cannot be augmented, and once SilgiPageMeta has a key, a meta key it does not declare fails. Only the app declares them, never a module
definePageMeta({ ssr: false })meta.ssr = false, or a routeRules entry
definePageMeta({ name, path, alias, validate, redirect, props, key, keepalive, pageTransition })none; the route name comes from the file, and a redirect is a redirect route rule or a load's redirect()
<NuxtPage>Outlet, for nested routes. The top-level page is the layout's children.
<NuxtLayout>none; layouts apply by name, from meta.layout
setPageLayoutnone
<NuxtLink to>Link with to, replace, prefetch (boolean), prefetchOn ("interaction" by default, where Nuxt's is "visibility"), noPrefetch, activeClass, exactActiveClass and ariaCurrentValue. It has no external prop.
useAsyncData in a page for first-render datauseAsyncData(fn, input) with a handler from #server; x.load.ts for a status or redirect decided before render, see Data

Composables and components

NuxtSilgiDifferences
useFetch(url, opts)useFetchoptions server, lazy, default, transform, pick, key, watch (Vue also watch: false), getCachedData(key, app, { cause }) and the $fetch ones. The key is made for you unless you pass key; the URL, query and body may be getters. immediate and dedupe as in Nuxt. No deep.
useAsyncData(key, fn, opts)useAsyncData(fn, input, opts) for a handler of the app, or useAsyncData(key, fn, opts)the key of a handler call is made from the handler and the input; a written key may be a getter; the function gets { signal }; options as above
useLazyFetch, useLazyAsyncDatathe same names, or useFetch(url, { lazy: true })see useLazyFetch
result data, pending, status, error, refresh, execute, cleardata, error, status, refresh(), execute(), clear()status is idle, pending, success or error; pending is true while status is pending
refreshNuxtData(keys?)useRouter().refresh(key?)refreshes the load data and the live async-data entries; a getCachedData sees cause === "refresh:hook", and "refresh:manual" for refresh() and execute(), as in Nuxt 5 (there is no "refresh")
useNuxtData(key)useAsyncDataEntry(key) in every adapter, also exported as useNuxtData by Vuereads the entry of key, whichever component started it, and writes data without changing status; the next run of the handler replaces it
clearNuxtData(key)clearAsyncData(app, key)app is useApp()
createUseAsyncData, createUseFetchthe same names
useState(key, init)useState(key, init)the key is optional: the build adds one per call site (build.keyedComposables)
clearNuxtState(keys?)clearState(keys?, { reset }) of the adapter, clearState(app, keys?, { reset }) of silgi/appputs the keys back to the init their useState gave; reset: false sets them to undefined
useCookie(name, opts)useCookie(name, opts)JSON-encoded as Nuxt's, with default, readonly, encode, decode and the options of setCookie; watch only in Vue. refreshCookie(name) is the same function in silgi/app and every adapter
useRoute()useRoute()has path, fullPath, params, query, hash, name, meta, matched (one { name, path, meta } per matched route, parent first)
useRouter()useRouter()navigate (and push), replace, back, forward, go, prefetch, refresh, resolve, beforeEach, afterEach, onError, isReady, getRoutes, hasRoute, currentRoute; no addRoute, removeRoute or options. See Routing
router.resolve(to)useRouter().resolve(to)gives { href, path, name?, params, query, hash } of an app path or a { name, params } location
navigateTo(to, opts)navigateTo(to, { replace, external, redirectCode }) of silgi/app and every adapter; useRouter().navigate(to, { replace, scroll, external }) in components; redirect(to, { status, external }) in a load and in route middleware; sendRedirect(event, to, { external }) in server routesa target on another origin needs external: true
abortNavigation()abortNavigation() of silgi/app, or return false, in route middlewarewith an argument it throws createError(error)
defineNuxtRouteMiddlewaredefineRouteMiddlewarethe arguments are (to, from, { state, app, event }); event is set on the server only. It runs in the browser on a navigation and guards no data: check in the handler
addRouteMiddleware()none at run time; modules use addRouteMiddleware of silgi/kit
onBeforeRouteLeave, onBeforeRouteUpdatethe same names in every adapterfn(to, from) answers false, a path, a location or nothing, as router.beforeEach does; it belongs to the page of the component, and a layout's never runs. For a confirm dialog use useBlocker. See Routing
useHead(input)useHead(input)title, titleTemplate, meta, link, script, style, htmlAttrs, bodyAttrs, and key on a tag. No tagPosition, tagPriority or textContent.
useSeoMetauseSeoMeta
useServerSeoMeta, useHeadSafenone
useRuntimeConfig()useRuntimeConfig() in components, with the same { public, app } shape; useRuntimeConfig(event) on the serverprivate keys only through useRuntimeConfig(event). Up to this change useRuntimeConfig() returned the public object itself; write useRuntimeConfig().public.<key>
useAppConfig, updateAppConfiguseAppConfig(); updateAppConfig(patch) deep-merges and tells the readerstyped from the config once silgi typegen ran: reading a key the config does not have fails tsc
useError, showError, clearError, createErrorthe same namescreateError takes status, statusText, message, data, fatal and cause; Nuxt's statusCode and statusMessage are accepted as deprecated names for status and statusText and warn once in dev
<NuxtErrorBoundary>ErrorBoundary with a fallback that gets { error, reset }
<ClientOnly>ClientOnly with fallbacknot in html
useNuxtApp()useApp()state, asyncData, hooks, config, head, cookies, error (browser only), adapter, and what plugins provide
defineNuxtPlugindefinePluginno parallel, dependsOn or enforce; order is by file name
useNuxtApp().hook, hooksapp.hooks.hookhooks app:created, app:adapter, app:mounted, page:start, page:finish, page:error, app:error
onNuxtReadythe app:mounted hook
useLoadingIndicator, <NuxtLoadingIndicator>useNavigation(), which is idle or loadingdraw the bar yourself
preloadRouteComponents, prefetchComponentsuseRouter().prefetch(to) and Link's prefetch
useRequestEvent, useRequestHeaders, useRequestHeader, useRequestURL, useRequestFetch, useResponseHeaderthe same names in every adapter, without the app argument; useResponseHeader(name) returns [value, set]. In a load: ctx.event, ctx.url, ctx.fetch, ctx.setHeaders. In server code outside a component: useEvent() of silgi/server (Server)on the server they read the request being rendered; in the browser they give undefined, {} and new URL(location.href). See useRequestEvent and the others
$fetch$fetchin silgi/app and silgi/server; FetchError has status, statusText, data, url
callOnce(key, fn, { mode })callOnce(key, fn, { mode }), "render" or "navigation"returns a promise; the server records the keys it ran in the page. See State
useHydration(key, get, set)useHydration(app, key, get, set) of silgi/app, in an app plugin
definePayloadReducer(name, reduce), definePayloadReviver(name, revive)definePayloadReducer(app, name, reduce) and definePayloadReviver(app, name, revive) of silgi/app, in an app plugin; app/transport.ts for a type that handlers send toosee Data
onPrehydrate, useRouteAnnouncer, useAnnouncer, usePreviewMode, useLayoutnone
<NuxtIsland>, server components, <NuxtImg>, <NuxtPicture>, <NuxtTime>, <DevOnly>, <NuxtClientFallback>, <Teleport> helpersnone
defineNuxtComponent, defineLazyHydrationComponentnone

A plugin that provides a helper and listens to a hook:

import { definePlugin } from "silgi/app";

export default definePlugin((app) => {
  app.hooks.hook("app:mounted", () => console.log("mounted"));
  return { provide: { hello: (name: string) => `Hello ${name}` } };
});

Forms and mutations

In a Nuxt app a form is usually a @submit.prevent handler that calls $fetch and then refreshes with refreshNuxtData. Silgi has a path with fewer steps: the mutation is a handler, Form action={fn} posts to it with and without JavaScript, and the page's data is read again by itself. A handler with a route keeps working as an API for other clients. See Forms and login and Handlers.

NuxtSilgiDifferences
$fetch("/api/x", { method: "POST", body }) in a submit handler, then refreshNuxtData()a handler and <Form action={fn}>the form posts without a handler of yours; after a success the page's load data and live entries are fetched again, with no call from you. A handler with a route and $fetch still work for an API that other clients call
readBody and a hand-written check in the POST routedefineHandler({ input, handler })the body is decoded (formToObject), validated by any Standard Schema, and a failure is a 400 with data: { issues, values }
createError({ statusCode: 400, data }) thrown and read from a FetchErrorreturn fail(400, data) and useAction(fn).failuredata is the handler's result on the page; no try and catch in the component
navigateTo("/done") after the $fetchreturn redirect("/done") from the handlera 303 without JavaScript
useFetch(url, { method: "POST", immediate: false }) and execute() for a mutationuseAction(fn)state, failure, pending and error; run(input) calls it
a pending flag in refuseFormStatus(), useForm(fn).pending, useAction(fn).pendingpendingRuns(app) lists the running calls with their formData
a $fetch to server/api/ that every client may calla handler with a routetyped from the handler; the id is public, so authorize inside
useState for an optimistic row, rolled back by handuseOptimistic(value, reduce)the row leaves when the data is fresh or the call fails
readMultipartFormData for uploadsform: { maxFileSize, maxFiles, onFile } on the handlerlimits answer 413
refreshNuxtDatauseRouter().refresh(), refresh(event, fn, input) on the serverrefresh(event, fn, input) sends the next read with the answer

A login that $fetches and then sets the shared state becomes a login handler. The Forms and login guide builds one.

Config keys

NuxtSilgiDifferences
ssrssr
—frameworkthe adapter: react, vue, preact, solid, svelte or html
app.baseURL, app.buildAssetsDir, app.head, app.rootIdthe sameapp.head.titleTemplate is a string here; useHead also takes a function
app.pageTransition, app.layoutTransition, app.keepalive, app.cdnURLnone
runtimeConfigruntimeConfigpublic goes to the browser. SILGI_<KEY> and SILGI_PUBLIC_<KEY> override a declared top-level key (the key in upper snake case, apiBase is API_BASE); there is no NUXT_ prefix and no nested keys, so override a whole object with JSON.
appConfigappConfigmerged with app/app.config.ts
routeRulesrouteRuleskeys prerender, ssr, swr, cache, headers, redirect, cors, proxy and noindex. No isr, appMiddleware or robots.
modulesmodulesSilgi modules only
extendsextends
aliasalias~, @ and #shared by default; #server is the door, server/index.ts, not an alias
hookshooksbuild hooks, listed in Modules; the names differ from Nuxt's
importsimportsautoImport (off) and dirs
componentscomponentsa boolean
devServerdevServerport, host, https
nitro.presetpresetbun, node, static, cloudflare, vercel, netlify
nitro.storagestoragememory, fs, sqlite, redis, s3, cloudflare-kv, cloudflare-r2, cloudflare-cache, netlify-blobs, vercel-blob, vercel-runtime-cache, azure-blob, github, planetscale, overlay, null, http and db; see Storage
nitro.devStoragedevStoragereplaces the mount of the same name under silgi dev and while prerendering
nitro.serverAssetsserver/assets/read with useStorage("assets:server"); a folder is not configured, see Server assets
nitro.scheduledTasksscheduledTaskscron expressions to task names, always in UTC
nitro.database, nitro.devDatabasedb.connections, and dev of a connectionwith @silgi/db
nitro.experimental.tasks, nitro.experimental.databasetasks: none to set; database: bun add @silgi/db and add it to modulestasks are on when a file is in server/tasks/; the database needs the module
nitro.prerenderprerenderroutes, crawl, failOnError, concurrency, ignore
A GraphQL module such as nitro-graphqlserver.graphqlSilgi has its own endpoint, at /api/graphql by default, with SDL in server/graphql/ and resolvers in *.resolver.ts files. The module's own options and plugins do not carry over. See GraphQL.
nitro (the rest), serverHandlers, devServerHandlersserver.trustProxy, server.maxBodySize; handlers through addServer in a module
vite, webpack, builder, build, esbuild, postcss, optimizationbuildminify, sourcemap, define, plugins (Bun plugins), external, compressPublic
sourcemapbuild.sourcemap"none", "linked" or "external"
srcDir, serverDir, buildDir, rootDir, devthe samebuildDir is .silgi; outputDir and publicDir are also keys
pluginsnone in the config; modules use addAppPlugin
cssnoneimport the CSS file from a layout or component
typescript.stricttypescript.strictdefault true, set in all the generated tsconfigs
typescript.typeCheckbuild.typecheck, or silgi build --typecheckchecks before bundling and stops the build on an error. silgi dev does not check types; run silgi typecheck
typescript.tsConfigtypescript.tsConfigthe object is merged into the compilerOptions of each generated file; there is no compilerOptions key around it, and no include or exclude. A module changes one file in the prepare:types hook. The tsconfigs are app, server, shared and node
experimental.typedPagesnone to set; typescript.typedPathsroute names, params and link paths are typed after silgi typegen, with no flag. typedPaths: false turns the path check off
$development, $production, $test, $envthe samesee below
experimental.envExpansionexperimental.envExpansion${NAME} in the strings of runtimeConfig. See Environment
typesDirnonesilgi typegen writes .silgi/
pages, router, dir, extensions, ignore, watch, watchers, experimentalnone
spaLoadingTemplate, unhead, vue, features, future, compatibilityDate, devtools, telemetry, logLevel, debug, test, theme, analyzeDir, appId, buildIdnone--analyze on build prints the largest modules per client chunk
import { defineConfig } from "silgi";

export default defineConfig({
  framework: "vue",
  ssr: true,
  app: { baseURL: "/", head: { titleTemplate: "%s · Shop" } },
  runtimeConfig: { apiSecret: "", public: { apiBase: "/api" } },
  routeRules: { "/blog/**": { prerender: true }, "/admin/**": { ssr: false } },
  storage: { cache: { driver: "fs", base: ".data/cache" } },
  preset: "node",
  imports: { autoImport: true },
  components: true,
});

Tasks, database, blob and storage

Nitro's tasks and database and NuxtHub's blob, database and key-value storage have Silgi counterparts. The names are close and the shapes are not, so read each row.

NuxtSilgiDifferences
defineTask({ meta: { name, description }, run({ payload, name, context }) })defineTask({ description, input, run(payload, ctx) }) in server/tasks/**the name is the path (server/tasks/db/seed.ts is db:seed); run gets the payload, then a context with name, scheduledTime, cron, signal, waitUntil and log; input is a Standard Schema; it returns the result itself, not { result }
runTask(name, { payload, context }) giving { result }runTask(name, payload) giving the resulttyped from the task files; a call with the same name and payload joins the pending run
scheduledTasks: { "0 9 * * 1": "report" }scheduledTasksthe same shape; always UTC; Silgi writes the weekday the way each platform reads it. devServer.tasks runs them under silgi dev
nitro task list, nitro task runsilgi task list, silgi task run <name> --payload '<json>'--prod runs with the production config
useDatabase(name) (db0)useDatabase(name) from @silgi/db/serverdb.sql`…` gives the rows, not { rows }; first, run, exec, batch and transaction; ${value} is always a bound parameter
nitro.database, nitro.devDatabasedb.connections.<name> and its devdevDatabase is the connection's own dev, and also covers the prerender
hubDatabase() (D1-style prepare().bind().all())useDatabase().sql on a d1 connectionwrite the query as a tagged template; batch([...]) is D1's batch; transaction is refused on D1
NuxtHub's server/database/migrations/*.sqlserver/db/migrations/<number>_<name>.sqlapplied at silgi dev start, silgi db migrate, or at build or start by db.migrate; D1 migrations are applied by Wrangler
Drizzle through NuxtHub's hub:dbyour own drizzle-ormsee the Drizzle recipe
hubBlob()useBlob() from @silgi/blob/serverput, get, head, list, serve and presign are there; hubBlob().del is useBlob().delete; dir and httpEtag have no counterpart (the option is base, and etag is on the object)
hubBlob().handleUpload, useUploada handler whose input holds a File, with ensureBlob; handleMultipartUpload and useMultipartUpload for big filessee Uploads
hubKV(), hubCache()useStorage("kv"), useStorage("cache")see KV and cache as mounts
useStorage(base) with a redis or mongodb driverstorage: { name: { driver: "redis" } }; a path driver for MongoDBsee MongoDB

Server utilities

The Nitro and h3 names on the left, with the Silgi name from silgi/server. See Server.

NuxtSilgiDifferences
defineEventHandlerdefineHandler({ route, method, handler }), with raw: true to read the event yourselfhandler(input, { event }); the path and the query (and the body, unless raw) arrive as input. onRequest and onBeforeResponse are defineMiddleware
defineCachedEventHandlercache on a GET handler; or defineCachedHandlercache takes maxAge, swr, staleMaxAge, varies, getKey, base, maxResolveTime
defineCachedFunctiondefineCachedFunction
defineNitroPlugindefinePlugin, listed in plugins of defineServerhooks request, beforeResponse, afterResponse, error, render:html, close
defineWebSocketHandlerdefineHandler({ route, websocket })hooks open, message, close, error with a peer; the bun preset only; a handshake from another origin gets 403 unless origin in websocket allows it (["https://app.example"], a function or "any")
useRuntimeConfig(event)useRuntimeConfig(event?)the event is optional; on cloudflare, vercel and netlify a call without it throws before the first request
useStorage(base)useStorage(mount)a mount named in storage; unnamed ones are in memory
useSessionuseSession(event, { password })sealed cookie; id, data, update, clear, and regenerate({ data }), which you call on login
getQuery, getValidatedQuerythe same
getRouterParam, getRouterParamsthe samegetValidatedRouterParams: none
readBody, readValidatedBodythe same413 above server.maxBodySize
readRawBodyreadBodyBytesh3's returns a string; Silgi's returns Uint8Array | undefined, so decode it with TextDecoder, or use readBody for text bodies. readRawBody stays as a deprecated alias through 1.x
readMultipartFormDatareadFormData, readMultipartreturn FormData; readMultipart(event, { maxFileSize, maxFiles, onFile }) streams the files and enforces limits
getRequestHeader, getRequestHeadersthe samegetHeader and setStatus stay as deprecated aliases through 1.x
setResponseHeader, setResponseHeaders, appendResponseHeadersetHeader, setHeaders, appendHeader
setResponseStatusthe same
getCookie, setCookie, deleteCookiethe same
sendRedirectsendRedirectreturns a Response: return it
createError, isError, sendErrorcreateError, isSilgiError; throw instead of sendError
sendStream, createEventStreamcreateEventStream(event) with push, close, send; or return a ReadableStream
sendNoContentreturn null
getRequestURL, getRequestIPthe samegetRequestURL uses forwarded host and proto only with server.trustProxy; getRequestIP(event, { xForwardedFor: true }) reads x-forwarded-for from the right (trustedHops, default 1)
handleCorshandleCorsreturns a Response for a preflight, else undefined
getMethod, event.methodevent.method
event.node.req, event.node.resevent.request, event.responseWeb Request; the response object holds status and headers
event.context, event.waitUntilthe same
proxyRequest, sendProxythe proxy route rule
the nuxt-security moduleserver.securityHeaders, server.originCheckNuxt adds no such headers or check without the nuxt-security module; Silgi sends X-Content-Type-Options, Referrer-Policy and X-Frame-Options and refuses cross-origin form posts unless server.securityHeaders or server.originCheck say otherwise
consola calls in server code, nitro-opentelemetry or a vendor SDKevent.log and useLogger(), server.tracing, server.metricsNitro has no built-in logger or tracing. Silgi has a leveled logger with redaction, a request id and request log, OpenTelemetry tracing with an OTLP exporter, Prometheus metrics and Server-Timing; see Observability
useNitroApp, defineRenderHandler, defineNitroErrorHandler, fromNodeMiddleware, serveStaticnone
defineTask, runTask, useDatabasedefineTask, runTask, useDatabasenot the same shapes; see below

A route that reads the event, as a Nitro handler does:

import { createError, defineHandler, getQuery, getRouterParam, setResponseStatus, useRuntimeConfig } from "silgi/server";

export const getPost = defineHandler({
  route: "/api/posts/:id",
  method: "GET",
  raw: true,
  handler(_, { event }) {
    const { draft } = getQuery(event);
    const id = getRouterParam(event, "id");
    if (!id) throw createError({ status: 400, message: "id is required" });
    setResponseStatus(event, 200);
    return { id, draft: draft === "1", site: useRuntimeConfig(event).public };
  },
});

Modules

defineNuxtModule is defineModule: meta.name, meta.configKey (defaulting to meta.name), defaults (an object or a function of the context), hooks and setup(options, silgi). Options merge as in Nuxt, inline options over the config key over defaults. A module types its options by adding to SilgiModuleOptions with declare module "silgi", so defineConfig checks them. See Modules.

Nuxt kitsilgi/kit
defineNuxtModuledefineModule
useNuxt()useSilgi(), or the second argument of setup
createResolvercreateResolver
useLoggeruseLogger; logger is its deprecated alias
addPluginaddAppPlugin
addServerPlugin, addServerHandleraddServer(file, { root? }): one door with the module's handlers, plugins and middleware
addImports, addImportsDir, addServerImportsaddImports; it applies to both app and server files. addImportsDir: none
addTemplate, addTypeTemplatethe same
extendPagesextendPages; Silgi also has addPage
addLayout, addRouteMiddlewarethe same
addVitePlugin, addWebpackPluginaddBunPlugin
updateRuntimeConfigupdateRuntimeConfig
addPrerenderRoutesthe prerender:routes hook
addComponent, addComponentsDir, installModule, addDevServerHandler, extendViteConfig, addBuildPlugin, defineNuxtConfigSchemanone

Internationalization

@nuxtjs/i18n is @silgi/i18n, a first-party module with the same route names (about___fr) and a smaller option set. Messages use an ICU MessageFormat subset, not vue-i18n's syntax: rewrite a | b plurals as {count, plural, one {a} other {b}} and linked messages (@:key) as plain text. See Internationalization.

@nuxtjs/i18nSilgiDifferences
modules: ["@nuxtjs/i18n"] and the i18n keymodules: ["@silgi/i18n"] and the i18n keya Vue-only module becomes one for all six libraries
locales: [{ code, iso, dir, name, file }]i18n.locales{ code, language, dir, name }: iso is language; file is none, the file is always locales/<code>.json
defaultLocalei18n.defaultLocale
strategyi18n.strategyprefix_except_default (the default) and prefix. no_prefix and prefix_and_default: not in v1
detectBrowserLanguagei18n.detect.cookie, i18n.detect.redirectthe cookie and Accept-Language choose where / redirects, and nothing else redirects. cookieKey is detect.cookie; detectBrowserLanguage: false is detect: { redirect: false, cookie: false }. alwaysRedirect, redirectOn, useCookie and fallbackLocale: not in v1
customRoutes: "config" and pagesi18n.pages{ about: { fr: "/a-propos" } } by route name; the params must stay the same
customRoutes: "page", defineI18nRoutenonepaths are config, not page meta
langDirnonethe folder is locales/ in the app root and in each layer, JSON only; every locale is one lazy chunk
lazy, bundle, compilation, vueI18nnonemessages are compiled at build time
baseUrli18n.baseUrla string origin, or { runtimeConfig: "siteUrl" } for a public runtime config key
useI18n().t, $tuseI18n().t of @silgi/i18n/<library>typed by typegen: a wrong key or argument fails tsc. Arguments are an object, t("home.hello", { name })
useI18n().n, d, $n, $dn, dIntl options; d() uses i18n.timeZone
useI18n().locale, localesuseLocale(){ code, language, dir, name, locales }; in Vue a ComputedRef, in Solid an Accessor
useI18n().setLocalenoneswitching is a navigation: Link to={switchLocalePath("fr")}
useLocalePath()useI18n().localePath(to)to is a path or { name, params, query, hash } with the base route name
useSwitchLocalePath()useI18n().switchLocalePath(code)the argument is the code and the current route is bound
useLocaleHead(), $localeHeadnone to callthe module writes lang, dir, hreflang, canonical and og:locale itself
<NuxtLinkLocale>Link with localePath(to)Link is not localized
differentDomains, domainnonenot in v1
skipSettingLocaleOnNavigate, customBlocks, experimental, parallelPlugin, types, debugnone

Types and environment

NuxtSilgiDifferences
useRoute("blog-slug")useRoute("blog-slug"), or useRoute<Route.Name>()types params of that route and never throws. Before silgi typegen the params are loose. See TypeScript
typed navigateTo and NuxtLink paths (typedPages)Link to, navigate, redirect and navigateToa literal path that no page, route or public file can match is a type error; { external: true } is needed for an external target
$fetch typed from server/api/$fetch typed from a handler's input and resultbody and query are checked for a literal URL; a Date in the result is a string (JsonSerialized)
useAsyncData data typeduseData(), useAsyncData(fn, input)Serialized<T>: what the payload carries, so a Date stays a Date
definePageMeta({ layout }) with typed namesmeta.layout and meta.middlewaretyped from the layouts and the named middleware; see TypeScript
useState with a typed keydefineState<T>(key, init?) and useState(key)the type comes from the key
useRuntimeConfig().publicuseRuntimeConfig().publicand #silgi/env/public, #silgi/env/private for values that app/env.ts declares and validates
import.meta.dev, import.meta.client, import.meta.server, import.meta.prerenderthe sameimport.meta.prerender is true while silgi generate renders
process.env.NUXT_PUBLIC_*SILGI_PUBLIC_<KEY>read when the server starts

$development applies to silgi dev, $test when NODE_ENV is test, and $production to every other command. $env.<name> applies for --mode <name>, over the block of the kind:

import { defineConfig } from "silgi";

export default defineConfig({
  framework: "react",
  build: { minify: true },
  runtimeConfig: { public: { apiBase: "/api" } },
  $development: { runtimeConfig: { public: { apiBase: "http://localhost:4000/api" } } },
  $test: { storage: { default: { driver: "memory" } } },
  $production: { build: { sourcemap: "linked" } },
  $env: {
    staging: { runtimeConfig: { public: { apiBase: "https://staging.example.com/api" } } },
  },
});

Layers apply their own blocks to their own keys. $env applies by --mode where Nuxt applies it by --envName. A block cannot hold rootDir, extends, dev or another block.

From React Router and Vite

React Router, ViteSilgiDifferences
import type { Route } from "./+types/post"import type { Route } from "./+types/[slug]"written by silgi typegen beside the page; the same +types spelling. Route.Params, Route.Name, Route.LoadContext, Route.LoadData, Route.Errors
Route.LoaderArgs, Route.ComponentPropsRoute.LoadContext; useData<Route.Name>(), useRoute<Route.Name>()no adapter passes params or data as props, so there is no ComponentProps
Route.ActionArgsnonea handler's input is typed; see Handlers
href("/blog/:slug", { slug })resolveRoute("blog-slug", { slug }) from silgi/app, or a { name, params } location, or a literal pathLink to and navigate check a literal path against the pages
import.meta.env.MODE, DEV, PROD, SSR, BASE_URLthe samereplaced in both bundles. A key that is not listed is undefined, on the server too
import.meta.env.VITE_*, envPrefiximport.meta.env.SILGI_PUBLIC_*, build.envPrefixthe default prefix is SILGI_PUBLIC_. A prefix that would expose Silgi's private SILGI_ variables is refused
vite build --mode staging, .env.stagingsilgi build --mode staging, .env.staging and .env.staging.localMODE is staging and PROD is true. NODE_ENV is not changed by --mode
envDirnonethe mode files are read from the app root, before the config file; --dotenv <file> adds a shared file
loadEnv(mode, dir, prefix)noneapp/env.ts with defineEnv declares, validates and types the variables
define: { __X__: … }build.define

Commands

NuxtSilgi
nuxt devsilgi dev
nuxt buildsilgi build
nuxt generatesilgi generate
nuxt previewsilgi preview; it serves bun, node and static builds, and prints the platform's command for the others
nuxt initsilgi init <dir> --framework <id>
nuxt preparesilgi typegen
nuxt typechecksilgi typecheck: writes the types, then runs tsc -b, vue-tsc -b or svelte-check from the app's node_modules; it prints Type errors: <n> and exits 1
--envName <name> on any command--mode <name>
nuxt analyzesilgi build --analyze, a text report
nuxt infosilgi info
nuxt add, upgrade, cleanup, devtools, modulenone