Design

Transformation Pipeline

How Obsidian markdown transforms into rendered HTML through multiple processing stages.
Properties4
Is BaseNo
Iconi-lucide-workflow
Order2
Tags #architecture #pipeline #transformation

The transformation pipeline is the beating heart of Lithos, converting your raw Obsidian markdown into production-ready HTML. This multi-stage process preserves the integrity of your source files while applying sophisticated transformations that make Obsidian syntax work seamlessly on the web. Understanding this pipeline reveals how Lithos achieves true Obsidian compatibility without compromising performance or developer experience.

The Full Pipeline: Vault to HTML

The journey from markdown file to rendered web page involves seven distinct stages, each with specific responsibilities and error handling.

The pipeline begins with Nuxt Content discovering markdown files. Rather than copying your vault into the project, Lithos uses a symlink strategy: the content/ directory is symbolically linked to your vault/ directory. This means Nuxt Content sees your notes as if they were inside the project, but they remain in their original location.

This symlink approach has profound implications. First, it ensures your vault is never modified by the build process. Every transformation happens in memory, preserving the source of truth. Second, it enables hot module reload during development—when you edit a note in Obsidian, the change propagates instantly to the dev server without manual refresh. Third, it allows Lithos to work with vaults stored anywhere on your filesystem, not just inside the project directory.

The symlink is typically created during project initialization or via the Lithos CLI. If you're manually setting up Lithos, running ln -s ../vault content (on Unix systems) or the equivalent on Windows establishes this critical link. Nuxt Content then treats the symlinked directory like any other content source, applying its file watchers and change detection mechanisms.

content:file:beforeParse Hook

Once Nuxt Content loads a markdown file, it passes through the content:file:beforeParse hook before any parsing occurs. This is where Lithos intervenes to perform raw text transformations that convert Obsidian syntax into formats Nuxt Content can understand.

The obsidian-transform module registers a handler for this hook, receiving the file object with its raw body string. At this stage, the content is pure markdown text—no AST, no parsed frontmatter, just a string. This is the ideal moment to apply regex-based substitutions that convert wikilinks, embeds, callouts, and other Obsidian-specific patterns into MDC components or standard markdown.

The module also injects file metadata at this stage. For example, it reads the file's modification time from the filesystem and inserts an mtime field into the frontmatter. This happens synchronously to avoid async complexity during the parsing phase. Similarly, the Daily Notes module uses this hook to detect date patterns and inject daily note metadata like isDailyNote: true and computed date fields.

Raw Text Transformations

The actual transformations happen through a series of regex replacements applied to the raw markdown string. Each transformation targets a specific Obsidian syntax pattern and converts it to a Nuxt-compatible equivalent.

Wikilinks are the most fundamental Obsidian feature. The pattern [[Page Name]] or [[page-name|Display Text]] must be converted into either standard markdown links or MDC component syntax. Lithos uses the remark-wiki-link plugin during the Remark phase, but link extraction happens here in the beforeParse hook.

The transformation extracts the target and alias from the wikilink syntax, then looks up the target in the permalinkMap (built during file discovery). This map resolves "Page Name" to its actual path, like /features/page-name, accounting for numeric prefixes, folder structure, and case sensitivity. The resolved path becomes the href for a standard markdown link, ensuring navigation works correctly.

// Before: [[Getting Started|Start here]]
// After: [Start here](/guide/getting-started)

If the target cannot be resolved (a broken link), Lithos can either leave it as-is (rendering as plain text), replace it with a special "broken link" component, or log a warning. The default behavior is graceful degradation: unresolved links render as plain text with a distinct style, preventing broken pages while signaling the issue visually.

Embeds:

Daily Notes

Daily notes are the backbone of journaling in Obsidian, and Lithos transforms them into a sophisticated blogging system. Your private daily reflections can become a public-facing blog with chronological navigation, RSS feed support, and elegant date-based URLs. This feature bridges personal knowledge management with public content creation, letting you write once in Obsidian and publish automatically to the web.

Automatic Detection and Transformation

Lithos intelligently recognizes daily notes based on your Obsidian configuration and transforms them into blog-style posts with beautiful URLs and metadata.

Date-Based Route Transformation

Daily notes follow a predictable naming pattern in Obsidian: YYYY-MM-DD.md. Lithos detects these files and transforms their URLs into a hierarchical structure that improves SEO and readability. Instead of exposing the raw filename, Lithos creates date-based routes like /daily-notes/2025/01/27, making your content feel like a professional blog rather than a collection of flat files.

The transformation happens automatically during the build process through the daily-notes module. When a markdown file matches the date pattern and resides in your configured daily notes folder, Lithos extracts the year, month, and day components and restructures the path accordingly. This means you never have to manually manage URL structures or worry about filename conventions beyond what Obsidian already expects.

This hierarchical routing also enables powerful navigation patterns. Readers can mentally parse the URL structure to understand the temporal context of a post, and you could potentially create archive pages by year or month by querying the content API for paths matching those prefixes. The date structure becomes a natural taxonomy without requiring manual categorization.

Configuration via .obsidian/daily-notes.json

Lithos reads your existing Obsidian daily notes configuration to ensure seamless compatibility. The .obsidian/daily-notes.json file, which Obsidian creates when you enable the Daily Notes core plugin, contains two critical pieces of information: the folder where daily notes are stored (e.g., "Daily Notes" or "Journal") and the date format pattern (typically "YYYY-MM-DD").

{
  "folder": "Daily Notes",
  "format": "YYYY-MM-DD"
}

The beauty of this approach is that you don't need to configure Lithos separately. If your vault already uses daily notes, Lithos automatically detects the folder and applies the appropriate transformations. If the configuration file is missing, Lithos falls back to sensible defaults, looking for files matching the YYYY-MM-DD pattern in any folder.

The folder name is also used to generate the base route segment. A folder named "Daily Notes" becomes /daily-notes, while "Journal" becomes /journal. Lithos normalizes the folder name by converting it to lowercase and replacing spaces with hyphens, ensuring URL-friendly paths regardless of your folder naming conventions.

Frontmatter Enhancement

During transformation, Lithos automatically injects metadata into each daily note's frontmatter to support blog-like functionality. This includes an isDailyNote flag that distinguishes these posts from regular documentation pages, a machine-readable date field in ISO 8601 format for sorting and filtering, and a human-readable displayDate that can be used in templates.

---
isDailyNote: true
date: 2025-01-27T00:00:00.000Z
displayDate: "Monday, January 27, 2025"
title: "2025-01-27"
---

This metadata enhancement happens transparently in the content:file:beforeParse hook, meaning it never modifies your source files. The enriched frontmatter exists only in the processed content that Nuxt Content reads, preserving the integrity of your vault while enabling advanced querying and display logic in your templates.

Date-Based Navigation and Discovery

Once your daily notes are transformed, Lithos provides several ways for readers to navigate through your journal chronologically.

Blog posts are most valuable when readers can easily move between them. Lithos automatically calculates the previous and next daily notes in chronological order, enabling you to add navigation links at the bottom of each post. This creates a linear reading experience, perfect for serialized content or progressive journaling.

The implementation leverages Nuxt Content's query API to fetch all daily notes, sort them by date, and find the immediate neighbors of the current post. You can implement this in your page template using the Structured Data components or by querying the content directly in a custom Vue component. The isDailyNote frontmatter flag makes filtering straightforward, ensuring only journal entries appear in the navigation chain.

This chronological linking creates a sense of continuity and progression. Readers who discover a single post can easily traverse backward to earlier thoughts or forward to see how ideas evolved. It transforms a collection of isolated notes into a cohesive narrative arc.

Archive and Listing Views

Beyond individual post navigation, readers often want to see an overview of all available entries. Lithos's Structured Data system makes it trivial to create archive pages, whether as a simple list of titles and dates or as a rich grid with excerpts and metadata.

The ObsidianBase component can query your daily notes folder and render it as a table, list, or card grid. By filtering for isDailyNote: true and sorting by date descending, you create a blog archive page with just a few lines of configuration. You can further enhance this with pagination (coming soon), tag filtering to show posts by category, or grouping by year and month for a chronological archive.

::obsidian-base{source="/daily-notes" layout="list" sort="date" direction="desc"}
::

These archive views serve multiple purposes. They act as a site map for search engines, improve discoverability for human readers, and provide a sense of the volume and consistency of your writing. A well-designed archive page can be one of the most visited pages on your site.

RSS Feed Generation

Modern blogs rely on RSS feeds for syndication, and Lithos automatically generates one for your daily notes at /rss.xml.

Automatic Feed Creation

The RSS feed is built at request time by the /server/routes/rss.xml.ts handler, which queries all content marked with isDailyNote: true, sorts by date, and formats it into RSS 2.0 XML. The feed includes standard elements like title, link, publication date, and description, making it compatible with all major feed readers.

Each feed item links directly to the daily note's public URL, and the publication date is derived from the date frontmatter field. This means your feed is always up-to-date and reflects the chronological order of your writing. Readers can subscribe in apps like Feedly, Reeder, or NetNewsWire and receive automatic updates whenever you publish new entries.

The feed is generated dynamically during development, but in production (with static site generation), it becomes a static XML file that loads instantly. This ensures fast performance while maintaining the dynamic convenience of server-side generation during development.

Feed Customization

The RSS handler includes sensible defaults, but you can customize the feed's metadata by setting environment variables. The NUXT_PUBLIC_SITE_URL variable controls the base URL for all links, ensuring they resolve correctly in production. You can also modify the feed title and description directly in the route handler if you want to brand it specifically for your journal.

NUXT_PUBLIC_SITE_URL=https://yourdomain.com

Future enhancements will add support for categories (via tags), custom feed limits, and full-text content inclusion (currently only descriptions are included). These features will make your RSS feed even more powerful and flexible for different reader preferences.

Integration with Frontmatter

Daily notes gain additional superpowers when you leverage frontmatter fields beyond the auto-injected metadata.

Date, Tags, and Type Fields

While Lithos automatically adds a date field, you can override it manually if you want to backdate a post or schedule it for future publication. The tags field allows you to categorize daily notes by theme, making it possible to create tag-filtered archive pages (e.g., "Show all posts tagged #reflection").

The type field provides semantic differentiation. You might set type: blog for public-facing posts and type: journal for personal entries, then filter in your templates to only show blog-type posts on the main archive page. This gives you fine-grained control over what appears in RSS feeds and public listings without requiring separate folders.

---
date: 2025-01-27
tags: [reflection, programming, learning]
type: blog
---

This frontmatter-driven approach means you can mix public and private notes in the same folder, deciding on a per-note basis what to expose. It's a flexible system that respects your evolving needs and workflows.

Custom Metadata for Rich Display

Beyond the standard fields, you can add custom frontmatter for richer displays. A cover field pointing to an image can be rendered as a featured image in card layouts. An excerpt field provides a hand-crafted summary for archive pages, overriding the auto-generated description. A featured boolean flag can highlight specific posts on your homepage.

These custom fields integrate seamlessly with the Structured Data system, which automatically detects and renders them based on their type. An image field shows a thumbnail, a URL field renders a link button, and a boolean checkbox appears as a toggle. This convention-over-configuration approach keeps your markdown clean while enabling sophisticated display logic.

Rendering Differences from Regular Notes

Lithos treats daily notes differently from documentation pages in subtle but important ways to optimize the reader experience.

Template Variations

Documentation pages typically use a dense, sidebar-heavy layout optimized for reference material and quick navigation. Daily notes, on the other hand, benefit from a cleaner, more spacious layout that emphasizes readability and immersion. You can create a custom page template that detects isDailyNote and adjusts the layout accordingly, such as widening the content column, removing the table of contents, or adding a publication date header.

In your [...slug].vue page component, you can check the isDailyNote flag in the page data and conditionally render different layouts. This allows you to maintain a consistent design system while tailoring the reading experience to the content type. Documentation feels like a reference manual; blog posts feel like articles.

Metadata Display and Bylines

Daily notes often benefit from prominent date display. Unlike documentation pages where the title is paramount, journal entries gain context from their temporal position. Adding a byline component that displays the displayDate ("Monday, January 27, 2025") at the top of the post immediately orients the reader.

You might also add social sharing buttons, estimated reading time, or tag pills—all features more common in blog layouts than documentation. The Backlinks system, while useful for both types of content, takes on a different flavor in daily notes. Backlinks in a journal entry might reveal recurring themes or topics that span multiple days, creating a narrative web of thought.

SEO and Open Graph Tags

Search engines and social media platforms expect different metadata from blog posts versus documentation. Daily notes benefit from Open Graph image tags (using the cover field), Twitter Card metadata, and structured data markup (JSON-LD) indicating an article type. Lithos can generate these tags automatically based on the isDailyNote flag, ensuring your journal entries look polished when shared on social media.

Documentation pages, conversely, might prioritize breadcrumb schema, software application metadata, or how-to schema, depending on the content. By differentiating daily notes at the rendering level, Lithos ensures each content type is optimized for its intended discovery and consumption pattern.

Tip
Use the Interactive Graph to visualize connections between your daily notes and other content. Journal entries often reference evergreen notes, creating a beautiful web of thought evolution over time.
Note
Daily notes are automatically excluded from the main documentation navigation but appear in the RSS feed and search results. This keeps your docs focused while making your blog discoverable.

Embeds use the exclamation mark prefix: ![[Other Note]]. These must be transformed into a custom MDC component that can fetch and render the target note's content. The transformation converts ![[target]] into ::note-embed{src="target"}, which Nuxt Content then parses as a component.

The NoteEmbed component, rendered on the client, uses Nuxt Content's queryContent API to fetch the target note and displays it in an expandable/collapsible card. This maintains the semantic meaning of the embed while adapting it to web UX patterns—inline embedding can be overwhelming, so the collapsible pattern gives readers control.

Image embeds (![[image.png]]) are handled similarly, but the component detects file extensions and renders an <img> tag instead of fetching markdown content. This automatic type detection simplifies authoring—you write embeds the same way in Obsidian, and Lithos figures out the rendering strategy.

Callouts: > !type Title

Obsidian callouts (also called admonitions) use a special blockquote syntax: > [!note] This is a note. Lithos transforms these into MDC component syntax: ::callout{type="note"}. The callout title and body are parsed and passed as component props.

The transformation must handle nested markdown within callouts, including inline formatting, lists, and code blocks. The regex carefully preserves indentation and structure, ensuring that complex callouts with multiple paragraphs or nested elements render correctly. The Callout component then applies Obsidian-style theming with color-coded icons and backgrounds based on the type (note, warning, tip, etc.).

Foldable callouts (> [!note]- Collapsed by default) are also detected, with the fold state passed as a prop. This enables progressive disclosure—readers expand callouts when they need additional context, keeping the main text uncluttered.

Inline Bases: dataview blocks

While Dataview is a plugin, not core Obsidian, Lithos supports basic inline query syntax by transforming ```dataview code blocks into ::base component calls. The transformation parses the dataview query (typically a simple TABLE or LIST FROM "folder" format) and maps it to equivalent ::base props like path, layout, and fields.

This transformation is lossy—Lithos doesn't support the full Dataview query language. Complex queries with expressions or formulas may not work. However, the most common pattern (list all notes in a folder, sort by date) translates cleanly, covering 80% of use cases without requiring users to learn a new syntax.

ABC Notation for Music

For users who embed musical notation using ABC notation (via plugins like ABC.js), Lithos can transform ```abc code blocks into a custom component that renders sheet music using the abcjs library. This is an optional transformation enabled via configuration, demonstrating the extensibility of the pipeline for domain-specific content types.

The transformation wraps the ABC code in a ::music-notation component, which renders on the client using a JavaScript library. This keeps the server-side transformation simple (just wrapping the code block) while delegating complex rendering to the client, where it can be hydrated interactively.

Remark/Rehype Plugin Chain

After the beforeParse transformations, Nuxt Content parses the markdown into an Abstract Syntax Tree (AST) using the Remark library. This AST then passes through a chain of Remark and Rehype plugins that apply further transformations.

remark-math and rehype-katex

Mathematical notation in LaTeX format (e.g., $\int x dx$ or $$\sum_{i=1}^n i$$) is handled by the remark-math and rehype-katex plugin pair. remark-math identifies math syntax in the markdown AST, and rehype-katex converts it to HTML with MathML or CSS-based rendering using the KaTeX library.

This two-stage approach is necessary because math syntax is markdown-level (it appears in the raw text), but rendering requires HTML-level transformations (inserting <span> elements with special classes and styles). The plugin chain enables this separation of concerns, keeping each transformation focused and composable.

KaTeX is preferred over MathJax because it's faster and produces smaller bundles. The server-side rendering means math appears instantly on page load, with no client-side JavaScript execution required for the initial render. This improves performance and accessibility, as screen readers can parse the generated MathML.

While basic wikilink transformation happens in the beforeParse hook, the remark-wiki-link plugin provides more sophisticated handling during the Remark phase. It has access to the full markdown AST, allowing it to resolve links in context—for example, handling links inside tables, list items, or nested blockquotes differently.

The plugin also integrates with the permalinkMap, using it to resolve ambiguous links. If multiple files have similar names (e.g., "Guide.md" in both /features/ and /examples/), the plugin can apply heuristics to choose the correct target based on folder proximity or explicit disambiguation in the link syntax.

This plugin is where Obsidian's "shortest unique path" logic is implemented. If you link to [[features/guide|Guide]] in Obsidian, the plugin resolves it to /features/guide instead of /examples/guide, matching Obsidian's behavior. This ensures high-fidelity compatibility with how links work in your local vault.

Custom Remark Plugins for Obsidian Syntax

Lithos includes several custom Remark plugins to handle edge cases in Obsidian syntax. For example, remark-obsidian-embeds processes embed syntax that was converted to ::note-embed{} in the beforeParse hook, ensuring the component AST node is structured correctly for Nuxt Content's MDC renderer.

Another custom plugin, remark-obsidian-tags, parses inline tags (e.g., #project/lithos) and converts them into structured data nodes. These tag nodes are indexed during the afterParse hook and made queryable via the content API. This allows you to create tag-based archive pages or filter content by tags in the Structured Data system.

These custom plugins demonstrate the extensibility of the pipeline. If you need to support a specific Obsidian plugin or community syntax, you can write a Remark plugin that integrates seamlessly into the existing chain, without modifying core Lithos code.

content:file:afterParse Hook

After the markdown is parsed and transformed into an AST, the content:file:afterParse hook fires. At this stage, the file object includes parsed frontmatter, a structured AST, and metadata like title and description. Lithos uses this hook for metadata extraction and graph construction.

The afterParse hook builds the permalinkMap, which maps every possible reference to a page (filename, title, aliases) to its canonical path. This map is critical for link resolution during the beforeParse phase of subsequent files.

For each file, the hook extracts the title, filename, and any aliases from the frontmatter. It then creates lowercase, normalized versions of each name and stores them in the map. For example, a file with title "Getting Started" and alias "Quick Start" creates map entries for "getting started", "quick start", and the filename "getting-started.md".

This map is stored in memory during the build process and serialized to .nuxt/permalink-map.json for use by API routes and client-side components. In development mode, the map is rebuilt on every file change to ensure link resolution stays accurate as you edit your vault.

The afterParse hook also extracts all outgoing links from each file by traversing the AST and collecting link nodes. These links are stored in the extractedLinks map, which records which files link to which other files. This data structure forms the basis of the Interactive Graph and the Backlinks system.

Link extraction at the AST level is more reliable than regex-based extraction from raw text because the AST disambiguates links from code blocks, inline code, and escaped syntax. For example, if you include a literal wikilink in a code example ([[example]]), the AST knows it's code and excludes it from the link graph.

The extracted links are paired with the permalink map to resolve link targets. If a link points to "Getting Started", the map resolves it to /guide/getting-started, and the graph records an edge from the current file to that path. This two-step process (extract, then resolve) ensures the graph uses canonical paths consistently.

Metadata Enrichment

During the afterParse hook, Lithos also enriches file metadata based on conventions. For example, if a file is in a folder called "blog", it might automatically receive a type: blog property. If a file has a cover property in its frontmatter, it's marked as hasCover: true to enable optimized rendering logic.

This convention-based metadata reduces manual configuration. You don't need to tag every blog post as type: blog if you organize them in a blog folder—the system infers it. This "convention over configuration" principle keeps your markdown clean while enabling sophisticated querying and filtering.

AST Transformation: Obsidian Syntax to MDC

The final transformation stage converts the Remark AST (which represents markdown structure) into an MDC-compatible AST that Nuxt Content can render. MDC (Markdown Components) is Nuxt Content's extension to markdown that allows Vue components to be embedded using special syntax.

Component Node Injection

During this stage, special syntax like ::callout{type="note"} is converted into AST nodes of type component. These nodes include a name property (the component name) and a props object (the component's props). Nuxt Content's renderer then maps these nodes to Vue components, hydrating them on the client.

The transformation must preserve slot content. For example, a callout with body text becomes a component node with a default slot containing the body's AST. This ensures nested markdown (bold text, links, code blocks) inside the callout renders correctly as part of the component's slot content.

Handling Nested Components

Obsidian supports nested structures, like a callout inside a list item or an embed inside a callout. The AST transformation must respect this nesting, creating a hierarchical component tree that mirrors the original structure. This is handled by recursive traversal of the AST, where each node is transformed in context of its parent.

If nesting conflicts arise (e.g., a component that doesn't support slots receives slot content), the transformation logs a warning and gracefully degrades by flattening the structure. This defensive design prevents build failures from edge cases while alerting developers to potential issues.

Preserving Markdown in Component Props

Some components receive markdown-formatted text as props (e.g., the title prop of a callout might include bold or inline code). The transformation must either pre-render this markdown to HTML or pass it as a raw string for client-side rendering.

Lithos typically pre-renders prop markdown to HTML strings, ensuring they appear instantly without client-side processing. This is done using Remark's html renderer, which converts the prop's AST subtree to an HTML string. The component then receives the HTML and injects it using v-html, with appropriate sanitization to prevent XSS.

Graph Data Extraction and Serving

The graph data extracted during the afterParse hook is serialized and exposed via the /api/graph endpoint, making it available to both server-side rendering and client-side components.

Data Serialization

After all files are processed, Lithos writes two JSON files to .nuxt/: permalink-map.json and extracted-links.json. These files contain the resolved permalink mappings and the raw link graph, respectively. Storing them as JSON allows the data to be loaded synchronously by API routes without re-parsing markdown.

The serialization format is optimized for size and lookup speed. The permalink map is a flat object with keys being normalized names and values being paths. The extracted links are an array of [source, target] tuples, which is compact and easy to iterate. This simple format ensures fast cold starts during development and minimal bundle size in production.

/api/graph Endpoint Construction

The /api/graph route reads the serialized JSON files and constructs the full graph representation. It queries Nuxt Content for metadata about each node (title, tags, folder), then combines it with the link data to produce a JSON response with nodes and edges arrays.

This server-side construction ensures the graph is always consistent with the current content. In development mode, the API reads from the .nuxt/ directory, which is regenerated on file changes. In production (SSG mode), the API is pre-rendered to a static JSON file at /api/graph.json, ensuring instant load times without server processing.

The API also performs defensive filtering. Broken links (targets that don't resolve to real pages) are excluded from the edges array, preventing the graph from showing phantom nodes. Nodes with no incoming or outgoing edges (orphans) can optionally be included or excluded based on configuration, giving you control over graph density.

Error Resilience and Debugging

The pipeline is designed to fail gracefully, logging warnings instead of crashing when it encounters unexpected input.

Try-Catch Wrapping

Each transformation stage is wrapped in try-catch blocks. If a regex fails to match, a link can't be resolved, or a component prop is malformed, the error is caught, logged to the console, and the transformation is skipped. The original content passes through unchanged, ensuring the page still renders (even if not perfectly).

This defensive approach is critical for iterative development. You can write a note with experimental syntax, and the build won't fail—it'll just log a warning. You can then fix the syntax and rebuild without restarting the dev server or losing your place.

Parse Error Placeholders

If a file has frontmatter syntax errors (invalid YAML) or markdown syntax errors (unclosed code blocks), Lithos injects a visible error placeholder at the top of the rendered page. This placeholder includes the error message and the file path, making debugging straightforward.

The page still renders, showing all content below the error. This is better than a blank page or a build failure, as it allows you to see the context of the error and fix it in place. In production builds, these placeholders can be disabled (causing errors to log silently), but in development, they're invaluable.

During the build, Lithos collects all unresolved wikilinks and outputs a summary report at the end. This report lists all broken links by source file, making it easy to audit your content for dead references. You can configure the build to fail (exit code 1) if broken links are detected, enforcing link hygiene in CI/CD pipelines.

The validation also detects ambiguous links (where multiple files could match the same wikilink) and warns about them. This prevents silent misrouting, where a link goes to the wrong page because two files have the same name in different folders.

Tip
Run npm run build regularly to see the link validation report. Fixing broken links improves SEO and reader experience, and the report makes it easy to find and fix them systematically.
Note
The transformation pipeline is synchronous during beforeParse and afterParse hooks to ensure deterministic ordering. Async operations (like fetching external data) should happen in Nuxt's build:before hook to avoid race conditions.