0.3.4 is the release after 0.3.2. There is no 0.3.3 on npm: it was prepared and never published, and its changes are part of 0.3.4 (the CHANGELOG says so). @devorajs/core and @devorajs/cli are 0.3.4, @devorajs/adapter-netlify is 0.2.4, and create-devora stayed at 0.3.2 but now pins ^0.3.4.
Catch-all routes
A file named [...slug].tsx matches one or more segments; [[...slug]].tsx also matches none. Both work for pages and for API routes. ctx.params.slug is the segments joined with / and is still a string, so code written for [id] routes compiles unchanged; ctx.catchAll.slug (or req.catchAll.slug in an API route) is the string[].
// apps/marketing/routes/docs/[...slug].tsx
import type { RequestContext } from "@devorajs/core";
import { PageShell } from "@devorajs/core";
export const renderMode = "ssg";
export function getStaticParams() {
return [{ slug: ["intro"] }, { slug: ["guide", "setup"] }];
}
export function loader(ctx: RequestContext) {
return { path: ctx.params.slug, segments: ctx.catchAll.slug };
}
export default function Docs({ data }: { data?: { path: string; segments: string[] } }) {
return (
<PageShell appName="marketing">
<h1>/docs/{data?.path}</h1>
<p>{data?.segments.length} segment(s): {data?.segments.join(" > ")}</p>
</PageShell>
);
}// apps/marketing/endpoints/files/[...path].ts
import { apiRoute } from "@devorajs/core";
export const methods = ["GET"];
export const handler = apiRoute((req) => ({
status: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path: req.params.path, segments: req.catchAll.path }),
}));Both files were built and served with devora build and devora start on the published 0.3.5 packages (catch-all routes have been there since 0.3.4; this was not re-run on 0.3.4 itself), with sitemap: true in the app's app.config.ts. The build printed pre-rendered (ssg/isr): /docs/intro, /docs/guide/setup. Requests returned:
GET /docs/intro 200
GET /docs/guide/setup 200 <h1>/docs/guide/setup</h1> 2 segment(s): guide > setup
GET /docs 404 (a catch-all needs at least one segment; [[...slug]] would match)
GET /docs/other/page 404 (not in getStaticParams(), so never rendered)
GET /api/files/a/b/c 200 {"path":"a/b/c","segments":["a","b","c"]}
GET /api/files 404 (JSON)
sitemap.xml lists /docs/guide/setup and /docs/introThe rules, from the pages guide: a static file wins over [param], which wins over [...slug]; the catch-all must be the last segment; each segment is URL-decoded once, and a segment that is empty, ., .., or contains /, \ or NUL (including as %2F, %5C, %00) never matches. A dot in a URL is no longer taken for a file: /docs/v1.2/intro reaches the catch-all unless a real file exists at that path.
A faster static path
devora start and the adapters now keep pre-rendered pages and built files in memory (64 MB in total, 2 MB per file; larger files are streamed from disk), check them against the disk at most once a second so a rebuild shows up without a restart, and send strong ETags (a repeat visit gets a 304), HEAD, Content-Length, and brotli or gzip for text types. The route table and the module imports are memoized instead of being worked out on every request. Content types are correct for .xml, .webmanifest, fonts, .avif, .pdf, .wasm and more.
On the benchmark's tiny pre-rendered page, on one machine, that took the page from about 14,700 requests per second on 0.3.2 to between 71,000 and 82,000 in two full runs on 0.3.4. Astro, SvelteKit and React Router measured about 36,000 to 38,000 on the same page. The page is a few hundred bytes and the load tool runs on the same machine, so this mostly measures the HTTP layer.
What an existing app has to check
The CHANGELOG lists these as behavior changes, with what to do:
- A file named
[...name].tsxused to be a single[param]whose name started with dots; it is now a catch-all. Rename it if you did not mean that. getStaticParams()values are percent-encoded in the output path. Nothing to do unless you readdist/staticby hand.sitemap.xmllists the URLs of dynamic routes that exportgetStaticParams().devora startanswers any method other than GET and HEAD to a public file, a built asset or a pre-rendered page with 405 andAllow: GET, HEAD. A form that posted to a pre-rendered page never ran an action there; post to anssrroute.- With
sitemapoff, a hand-writtenpublic/sitemap.xmlis now served; it used to 404 in production.
The full table is on Upgrading.