Repository: streamlit/streamlit
File: frontend/lib/src/components/core/Block/ElementNodeRenderer.tsx (TypeScript)

Code before:
            endpoints={props.endpoints}
            {...elementProps}
          />
        </ElementContainer>
      )
    }

    case "json":
      return (
        <ElementContainer
          node={node}
          config={ElementContainerConfig.LARGE_ELEMENT}
          isStale={isStale}
        >
          <Json element={node.element.json as JsonProto} {...elementProps} />
        </ElementContainer>
      )

    case "markdown": {
      // Markdown "auto" width behavior:
      // When markdown has no explicit width config, apply container-aware sizing:
      // - In horizontal layouts: content width (fit-content)
      // - In vertical layouts: stretch (100%)
      let config = ElementContainerConfig.DEFAULT
      if (!node.element.widthConfig) {
        config = new ElementContainerConfig({
          styleOverrides: {
            width: isInHorizontalLayout ? "fit-content" : "100%",
          },
        })
      }

      return (
        <ElementContainer node={node} config={config} isStale={isStale}>
          <Markdown
            element={node.element.markdown as MarkdownProto}
            {...elementProps}
          />
        </ElementContainer>
      )
    }

    case "metric":
      return (
        <ElementContainer
          node={node}
          config={ElementContainerConfig.DEFAULT}
          isStale={isStale}
        >
          <Metric
            element={node.element.metric as MetricProto}

Review comment (performance):
**suggestion:** This re-creates `new ElementContainerConfig()` on each render. Even if behavior is correct, it can make downstream hooks/components do extra work.

We also have a couple places in `ElementNodeRenderer` that recently started using this pattern, and our Playwright perf metrics show small regressions around the same timeframe:
- [Long animation frames total duration (ms) and Main mount total duration (ms) in test_mega_tester_app_rendering_performance](https://issues.streamlit.app/playwright?tab=Runs&test=test_mega_tester_app_rendering_performance&show_mean_line=true&show_boxplot=true&load_all_metrics=false&y_domain_includes_zero=false)
- [Main mount total duration (ms) in test_basic_app_performance](https://issues.streamlit.app/playwright?tab=Runs&test=test_basic_app_performance&show_mean_line=true&show_boxplot=true&load_all_metrics=false&y_domain_includes_zero=false)

Could we refactor this to keep the config stable across renders to reduce unnecessary churn?

Code after:
      )
    }

    case "json":
      return (
        <ElementContainer
          node={node}
          config={ElementContainerConfig.LARGE_ELEMENT}
          isStale={isStale}
        >
          <Json element={node.element.json as JsonProto} {...elementProps} />
        </ElementContainer>
      )

    case "markdown": {
      // Markdown "auto" width behavior:
      // When markdown has no explicit width config, apply container-aware sizing:
      // - In horizontal layouts: content width (fit-content)
      // - In vertical layouts: stretch (100%)
      const config = node.element.widthConfig
        ? ElementContainerConfig.DEFAULT
        : isInHorizontalLayout
          ? new ElementContainerConfig({
              styleOverrides: { width: "fit-content" },
            })
          : ElementContainerConfig.FULL_WIDTH

      return (
        <ElementContainer node={node} config={config} isStale={isStale}>
          <Markdown
            element={node.element.markdown as MarkdownProto}
            {...elementProps}
          />
        </ElementContainer>
      )
    }

    case "metric":
      return (
        <ElementContainer
          node={node}
          config={ElementContainerConfig.DEFAULT}
          isStale={isStale}
        >
          <Metric
            element={node.element.metric as MetricProto}
            {...elementProps}
          />
        </ElementContainer>
      )