Skip to content

Feat/code block cls lower lazy boundary#392

Merged
haydenbleasel merged 4 commits intovercel:mainfrom
PaulSenon:feat/code-block-cls-lower-lazy-boundary
Feb 18, 2026
Merged

Feat/code block cls lower lazy boundary#392
haydenbleasel merged 4 commits intovercel:mainfrom
PaulSenon:feat/code-block-cls-lower-lazy-boundary

Conversation

@PaulSenon
Copy link
Contributor

@PaulSenon PaulSenon commented Feb 15, 2026

Description

Codeblocks were causing CLS because of spinner fallback on lazyloading the full Codeblock component.
While it makes sense to lazyload the highlighter (heavy) it does not make sense to lazyload all the UI shell.
So now I lowered the lazyload down one level and use the existing codeblock shell as placeholder without highlighting.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Refactoring (no functional changes)

Related Issues

Fixes #391

Changes Made

Core commit

  • I remove the Suspense boundary down 1 layer from packages/streamdown/lib/components.tsx to packages/streamdown/lib/code-block/index.tsx
  • I moved the heavy part to lazyload in a new sub component: packages/streamdown/lib/code-block/highlighted-body.tsx
  • packages/streamdown/lib/code-block/index.tsx is now lazyloading the new packages/streamdown/lib/code-block/highlighted-body.tsx sub component, while showing unhighlighted text with CodeBlockBody in fallback

Bonus commit

because linter wasn't working according to contribution guidelines, and new linter had small fixable failures. But if this should be another PR let me know and I get rid of this commit

  • Updated outdated linter instructions in CONTRIBUTING.md
  • Some linter fix unrelated to my dev

Testing

Just added unit test to make sure we see content before the lazy highlighted-body.tsx resolves but have the highlight callback run only once it loads.

  • All existing tests pass
  • Added new tests for the changes
  • Manually tested the changes

Test Coverage

Screenshots/Demos

before: demo
after: demo

Checklist

  • My code follows the project's code style
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have created a changeset (pnpm changeset)

Changeset

  • I have created a changeset for these changes

Additional Notes

For full CLS free codeblock we also need to remove the css virtualisation. But this impact render performance so I kept it out of scope. For those who like me want 100% stable render, just add this css:

[data-streamdown="code-block"] {
  content-visibility: visible !important;
  contain-intrinsic-size: none !important;
}

This will fix an initial scroll to bottom on a chat to be pixel perfect in a layoutEffect. But mind virtualising the chat history if it's big, to compensate de css virtualisation loss.


First time ever contributing to any OSS. Don't be harsh but don't hesitate to tell me if I did something wrong !

@vercel
Copy link
Contributor

vercel bot commented Feb 15, 2026

@PaulSenon is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

@PaulSenon
Copy link
Contributor Author

Before

Enregistrement.de.l.ecran.2026-02-16.a.13.36.00.mov

After

Enregistrement.de.l.ecran.2026-02-16.a.13.36.37.mov

@PaulSenon PaulSenon marked this pull request as ready for review February 16, 2026 04:40
@haydenbleasel
Copy link
Contributor

awesome, thanks @PaulSenon 🚀

@haydenbleasel haydenbleasel merged commit 15645da into vercel:main Feb 18, 2026
2 of 3 checks passed
@PaulSenon
Copy link
Contributor Author

heyy @haydenbleasel sorry to bother, but I think you might have get rid of the older suspense boundary removal.
It does basically revert thing to previous way of loading codeblock...

in packages/streamdown/lib/components.tsx

- const CodeBlock = lazy(() =>
-  import("./code-block").then((mod) => ({ default: mod.CodeBlock }))
- );

(and more)

see my original commit

@haydenbleasel
Copy link
Contributor

ah sorry must have been an issue with the rebase, i'll fix in a new commit.

haydenbleasel added a commit that referenced this pull request Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove codeblock layout shift (CLS)

2 participants