Skip to content

Content system: nested subdirectories and injectContent limitations #2318

@DaSchTour

Description

@DaSchTour

Summary

When building a documentation/help center with hierarchical content (categories as subdirectories), the AnalogJS content system has several undocumented limitations that make nested content structures surprisingly difficult to implement.

What I tried to build

A docs section at /docs with articles organized in categories:

src/content/docs/
  erste-schritte/
    willkommen.md      → /docs/erste-schritte/willkommen
    erster-upload.md   → /docs/erste-schritte/erster-upload
  assets/
    hochladen.md       → /docs/assets/hochladen

Problems encountered

1. injectContentFiles does not include content body

The ContentFile interface defines content?: string | object, but injectContentFiles() only returns filename, attributes, and slug — never the actual markdown content. This is not documented.

If you use injectContentFiles to find articles and then try to render with <analog-markdown [content]="article.content">, it fails with TypeError: Cannot read properties of undefined (reading 'render') because content is undefined.

Suggestion: Document clearly that injectContentFiles = metadata only, injectContent = metadata + content body.

2. injectContent with subdirectory doesn't work with nested directories

Using injectContent({ param: 'slug', subdirectory: 'docs' }) works when all .md files are directly inside src/content/docs/. But if articles are in subdirectories like src/content/docs/erste-schritte/willkommen.md, the slug from a [...slug] catch-all route becomes erste-schritte/willkommen (contains /).

Looking at the source code (analogjs-content.mjs:119-122):

// If slug contains path separators, treat it as root-relative to /src/content
const newBase = slug.includes('/')
    ? `/src/content/${slug}`
    : `${filePath}/${slug}`;

When the frontmatter slug contains /, the lookup becomes root-relative to /src/content/, completely ignoring the subdirectory prefix. This means:

  • subdirectory: 'docs' + slug erste-schritte/willkommen → should resolve to src/content/docs/erste-schritte/willkommen.md
  • But actually resolves to src/content/erste-schritte/willkommen.md (ignoring docs/)

There's no way to use both subdirectory and nested paths together.

3. contentDir in prerender config doesn't scan subdirectories

The contentDir option in vite.config.ts only scans .md files at the top level of the specified directory. Files in subdirectories are not discovered. This requires either:

  • Listing each subdirectory separately as its own contentDir entry
  • Flattening the content structure

Suggestion: Support recursive scanning or document this limitation.

4. No documented pattern for hierarchical/nested content

The documentation shows flat content structures (e.g., src/content/blog/my-post.md). There's no guidance on:

  • How to organize content in category subdirectories
  • How to use [...slug] catch-all routes with content
  • The interaction between frontmatter slug values containing / and the resolution logic

Workaround

I ended up flattening all docs articles into src/content/docs/ (no subdirectories) and using frontmatter metadata (category, categoryTitle) to maintain the logical hierarchy. This works but loses the benefit of filesystem organization.

Environment

  • @analogjs/platform: 2.3.1
  • @analogjs/content: installed with platform
  • Angular: 21
  • Node: 24

Suggestions

  1. Document injectContentFiles vs injectContent — clarify that only injectContent loads the content body
  2. Fix or document subdirectory + nested slug interaction — either make subdirectory work correctly with path-containing slugs, or document why it doesn't
  3. Support recursive contentDir scanning — or document that only top-level files are discovered
  4. Add a "hierarchical content" recipe — showing how to build category-based documentation with AnalogJS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions