vuejs/vue · error · Error

render cannot be called without a template.

Error message

render cannot be called without a template.

What it means

Thrown by TemplateRenderer.render when called but parsedTemplate is null — meaning the renderer was created without a template option (template was undefined/false). render() is meant to wrap rendered app content with the surrounding HTML shell; without a parsed template there is nothing to wrap with. This is usually an internal path: createRenderer calls templateRenderer.render only when template is set, so hitting this means an explicit direct call or a logic regression.

Source

Thrown at packages/server-renderer/src/template-renderer/index.ts:113

    const renderer: any = this
    ;['ResourceHints', 'State', 'Scripts', 'Styles'].forEach(type => {
      context[`render${type}`] = renderer[`render${type}`].bind(
        renderer,
        context
      )
    })
    // also expose getPreloadFiles, useful for HTTP/2 push
    context.getPreloadFiles = renderer.getPreloadFiles.bind(renderer, context)
  }

  // render synchronously given rendered app content and render context
  render(
    content: string,
    context: Record<string, any> | null
  ): string | Promise<string> {
    const template = this.parsedTemplate
    if (!template) {
      throw new Error('render cannot be called without a template.')
    }
    context = context || {}

    if (typeof template === 'function') {
      return template(content, context)
    }

    if (this.inject) {
      return (
        template.head(context) +
        (context.head || '') +
        this.renderResourceHints(context) +
        this.renderStyles(context) +
        template.neck(context) +
        content +
        this.renderState(context) +
        this.renderScripts(context) +
        template.tail(context)

View on GitHub (pinned to 9e88707940)

Solutions

  1. Provide a template option when creating the renderer: createRenderer({ template: '<html>...<!--vue-ssr-outlet-->...</html>' }).
  2. If you called render() directly, guard it: if (!renderer.parsedTemplate) return content.
  3. Use a separate renderer instance for full-page rendering vs. asset rendering.

Example fix

// before
const renderer = createRenderer({}) // no template
const html = renderer.templateRenderer.render(content, ctx) // throws

// after
const renderer = createRenderer({
  template: '<!DOCTYPE html><html><head></head><body><!--vue-ssr-outlet--></body></html>'
})
Defensive patterns

Strategy: validation

Validate before calling

function assertTemplateSet(templateRenderer: any): void {
  if (!templateRenderer.parsedTemplate) {
    throw new Error(
      'TemplateRenderer.render called without a template. Pass a template to createRenderer.'
    )
  }
}

// if calling render directly:
assertTemplateSet(renderer.templateRenderer)
renderer.templateRenderer.render(content, ctx)

Type guard

function hasParsedTemplate(tr: any): boolean {
  return tr.parsedTemplate != null
}

Try / catch

// Internal API — prefer not to call render() directly.
// Instead, ensure createRenderer was built with a template:
if (!options.template) {
  throw new Error('Cannot use full-page rendering without a template option')
}
const renderer = createRenderer(options)

Prevention

When it happens

Trigger: Manually calling templateRenderer.render(content, ctx) on a renderer built without a template. Or a code path in createRenderer.renderToString that reaches templateRenderer.render despite template being falsy due to a bug or monkeypatched options.

Common situations: A custom integration reaches into the internal templateRenderer instance. A renderer was created for asset/preload rendering only (no template) but later used for full-page rendering. Refactor that swapped renderer instances.

Related errors


AI-assisted analysis of vuejs/vue@9e88707940 (2026-08-11). Data as JSON: /api/errors/8eed72ec2b5b99b0. Report an issue: GitHub.