Skip to content

Search tags, filters, functions and guides.

JavaScript API / Errors and checks

JavaScript API

Errors and checks

Handle errors and check templates before rendering.

When something goes wrong, render rejects with an error that says which template failed, where, and often how to fix it. render never returns half a page. Streaming works differently once part of the page is sent.

KilnRenderError: Unknown variable "titel" in pages/page.kiln at line 4, column 9. Did you mean "title"?

  3 | {% block content %}
> 4 |   <h1>{{ titel }}</h1>
    |          ^^^^^
  5 | {% endblock %}

Error types

There are two kinds of error, both exported from @lucidkiln/core:

  • KilnSyntaxError means a template couldn't be parsed. Its diagnostics list every problem found, not just the first.
  • KilnRenderError means something failed while rendering, such as a missing variable or a function that threw.
import { KilnRenderError, KilnSyntaxError } from "@lucidkiln/core";

try {
	return await kiln.render("pages/page", context);
} catch (error) {
	if (error instanceof KilnRenderError && error.cause instanceof NotFound) {
		return notFoundPage();
	}
	throw error;
}

A KilnRenderError has these properties:

PropertyDescription
codeA stable code, such as undefined-variable or limit-steps
template, line, columnWhere the error happened
frameThe lines of source around the error, with a marker
templateStackThe templates involved, from the page down to the one that failed
causeThe original error, when your code threw: one of your functions, a getter in your data, your loader or your cache store

Checking templates

check parses a template and returns its problems without rendering it. Pass the source as the second argument to check text that isn't saved yet, such as a template being edited in a CMS.

const diagnostics = await kiln.check("pages/home", draftSource);

for (const problem of diagnostics) {
	console.log(`${problem.start.line}:${problem.start.column} ${problem.message}`);
}

As well as syntax errors, check looks at the other templates this one uses, when their names are written out in full rather than built at render time. It reports:

  • A template that doesn't exist, unless the include says ignore missing.
  • An include or component that passes a prop the template doesn't declare, leaves out a required prop, or passes a written-out value of the wrong type, such as variant: "danger".
  • A page with props that extends a layout, when the layout needs a prop the page neither declares nor sets. The page only accepts its own props, so the layout could never get it. A page with a rest prop passes everything on, so it isn't reported.
  • A warning for a variable that a template with props reads but doesn't declare, set or get from globals. Layouts may read their page's variables, so declare those as props to remove the warning.
  • A warning, in the same templates, for a variable a macro reads that is neither one of its parameters nor a global. A macro can't see the template's variables.

It also warns about obsolete and inaccessible HTML, such as an image with no alt. See HTML checks.

Apart from missing templates and HTML checks, these checks follow strict mode, so they are skipped when strict is off. check only knows what a template is passed when it can see the call, so it doesn't report unknown variables in templates without props.

Each diagnostic has a code, a message, a severity of "error" or "warning", the template, and start and end positions with a line and column. Codes are stable, so tools can match them.

Analysing templates

analyze reports what a template uses: the variables it reads, its props, the variables it sets at its top level, the functions, filters and tests it calls, the templates it extends, includes or imports, and its blocks and macros. Use it to build tooling, or to work out which data a page depends on.

const analysis = await kiln.analyze("pages/post");

analysis.functions; // ["document"]
analysis.templates; // [{ kind: "extends", name: "layouts/base.kiln", ... }]

A name built at render time, such as "sections/" ~ section.type, is reported with a name of null.

Reading the markup

templateText returns a template's source with every tag, output and comment, and the content of verbatim, replaced by spaces. Line breaks stay, so every offset, line and column still points at the same place. Hand it to HTML tools, such as a validator or an HTML language service, so they see only the markup you wrote.

import { templateText } from "@lucidkiln/core";

templateText('<a href="{{ url }}">{% if new %}New{% endif %}</a>');
// '<a href="         ">            New           </a>'