docs(blog): add client generator intro blog post - #399
Conversation
- Add /blog/agent-friendly-sdks as a React page with inline hero diagram and a reviewer-style CTA card - Support react-frontmatter for .page.tsx blog posts in the theme plugin - Add blog-recent-posts shared data (top 4) and a shared RecentPosts component that excludes the currently open post; localize the BlogPost template to use it - Add api-descriptions:openapi and api-lifecycle:sdks blog categories Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…indenting The 0-indent lists markdownlint wants break Markdoc tag parsing in the project build, so keep the original indentation and skip the file like other markdoc-heavy pages.
There was a problem hiding this comment.
marketing-site AI Review: 🟢 Completed
Redocly Agent has reviewed your changes and found 4 potential issue(s).
Note
Low Risk
This PR strictly adds static marketing content and a metadata update. It does not alter core application logic, production systems, or existing UI themes. The complex React-based modifications and theme changes described in the initial PR description were reverted during the review process, safely isolating the changes to a standard Markdown file.
Overview
Adds a new Markdown blog post announcing redocly generate-client, an open-source tool for generating agent-friendly SDKs from OpenAPI descriptions. To simplify the PR, the author reverted the initial custom React page implementation in favor of a standard Markdown file paired with an SVG hero diagram. Additionally, the PR updates blog-metadata.yaml to introduce a new sdks subcategory under the API lifecycle section and sets the publication date to August 31, 2026.
- Use root-relative docs links in the CTA - Revert to api-specifications:openapi category (api-descriptions to be handled in a separate PR) - Dim the lead paragraph and add space below it - Highlight the steps tagline with TextGradient and add spacing - Use the theme CodeBlock with copy control for all code samples
| No account, no config required. Flags or a <code>redocly.yaml</code>{' '} | ||
| <code>client</code> block, your choice. |
There was a problem hiding this comment.
Restructured: the post is plain markdown now, and every code block is separated by text.
| import React from 'react'; | ||
| import styled from 'styled-components'; | ||
|
|
||
| export default Page; |
There was a problem hiding this comment.
Why do we need to modify Page in theme? It looks a better option to either modify it in the marketing pages or to enhance a specific page with the RecentPosts component. What do you think?
There was a problem hiding this comment.
I would move components related to Blog from marketing-pages lib to this repo, but I would do it in separate PR.
The problem is in the shared template itself, every markdown post renders through BlogPost, and whichever post is newest lists itself in "Latest from our blog". So enhancing a single page wouldn't cover it.
There was a problem hiding this comment.
Update: reworked the PR to keep it minimal — the post is plain markdown now and all theme changes are reverted, so this file is untouched. The recent-posts fix comes in the separate PR together with moving the Blog components into this repo.
| data: { content, frontmatter }, | ||
| } = await context.cache.load(route.fsPath, 'markdown-frontmatter'); | ||
| // React blog posts export `frontmatter`; markdown posts use YAML frontmatter | ||
| const isReactPage = /\.page\.tsx?$/.test(route.fsPath); |
There was a problem hiding this comment.
Using regext seems fragile to me.
There was a problem hiding this comment.
Agreed, switched from the path regex to the route's own type: the Realm markdown plugin stamps its routes with metadata.type === 'markdown', so the loader choice now branches on that instead of the filename shape.
There was a problem hiding this comment.
Update: this change is out of the PR entirely — the post is markdown now, so no react-frontmatter loading is needed.
| <div> | ||
| <StepLabel>Run one command</StepLabel> | ||
| <StepHint> | ||
| No account, no config required. Flags or a <code>redocly.yaml</code>{' '} |
There was a problem hiding this comment.
Actually, some kind of config is still requred (either flags or redocly.yaml are necessary for generating).
There was a problem hiding this comment.
Reworded. Strictly, every option has a default (--output falls back to clientOutput / .client.ts), so the accurate claim is "no account, no config file" — the text now says that, with flags/redocly.yaml as optional customization.
| "import { configure, listMenuItems, getOrderById } from './client.js';", | ||
| '', | ||
| 'configure({ auth: { bearer: token } }); // sent only where an operation requires it', | ||
| '', | ||
| 'const menu = await listMenuItems({ query: { limit: 10 } });', | ||
| "const order = await getOrderById({ path: { orderId: 'ord_01khr…' } });", |
There was a problem hiding this comment.
| "import { configure, listMenuItems, getOrderById } from './client.js';", | |
| '', | |
| 'configure({ auth: { bearer: token } }); // sent only where an operation requires it', | |
| '', | |
| 'const menu = await listMenuItems({ query: { limit: 10 } });', | |
| "const order = await getOrderById({ path: { orderId: 'ord_01khr…' } });", | |
| "import { client } from './client.js';", | |
| '', | |
| 'client.configure({ auth: { bearer: token } }); // sent only where an operation requires it', | |
| '', | |
| 'const menu = await client.listMenuItems({ query: { limit: 10 } });', | |
| "const order = await client.getOrderById({ path: { orderId: 'ord_01khr…' } });", |
It's easier to understand this way (you configure a client, then invoke something from that same client).
There was a problem hiding this comment.
The sample shows what the generator actually emits: the TypeScript SDK exports each operation as a named function plus a module-level configure (see the use-generated-client guide). A client. object would misrepresent the default surface — instance style exists via createClient, but flat imports are the default DX.
| <div className="frame"> | ||
| <HeroDiagram /> | ||
| </div> | ||
| <figcaption> |
There was a problem hiding this comment.
If you want text "As agents become part of engineering...." to remain large, let's add separator or more spacing, that will make it easier to read
There was a problem hiding this comment.
Resolved by converting the post to plain markdown — it uses the standard blog typography now, no custom sizes.
| exhaustively tested, and already carrying Redocly's production traffic. | ||
| </p> | ||
|
|
||
| <CtaCard> |
There was a problem hiding this comment.
Those custom elements are gone — the post is plain markdown now.
| </p> | ||
|
|
||
| <p> | ||
| One caveat, stated plainly: the command is still experimental, flags and output may |
There was a problem hiding this comment.
One caveat, stated plainly:
This is how AI talks 😄. Could you shift the text a little bit towards human-like flavour?
|
I think it might be better to style Right now, it’s not clear if it’s a takeaway, transition, or heading, and quote style would make it easier to read as an aside. |
| (normalized to 3.x before generation). | ||
| </p> | ||
|
|
||
| <h2>Skills first</h2> |
There was a problem hiding this comment.
I was reading this section with no context, as i didn't review the client generator PR and found, that i don't understand the concept of the skills, why we need them, what purpose each skill serves and what will be inside each skill. So, i think it is better to add similar sentence to:
The design ships as agent skills. Every generator carries its own design document, and ejecting a generator drops it into your repo as a skill (.claude/skills/-generator/) beside the authoring guide.
Because we introduced a new concept for users and only us know what it is. AI generated for me example and from it i understand what it is:
$ redocly eject-generator zod
generators/zod/… # the generator source, now yours
generators/AGENTS.md # pointer, managed between markers
.claude/skills/client-generators/SKILL.md # HOW to write a generator (142 lines)
.claude/skills/zod-generator/SKILL.md # WHY this one is built this way (54 lines)
There was a problem hiding this comment.
Agreed, added a one-sentence definition of skills and the actual file layout eject produces.
There was a problem hiding this comment.
Good point — added a one-sentence definition of skills and the actual file layout eject produces (verified against the eject-generator command docs).
Co-authored-by: Illia Adamchuk <illia.adamchuk@redocly.com>
…egex Realm's markdown plugin stamps its routes with metadata.type === 'markdown', so branch on that rather than matching the .page.tsx filename shape. Also skip any post whose frontmatter cannot be loaded.
- Rewrite the post as a standard markdown blog post; the diagram is an SVG image in the body - Revert all theme changes (BlogPost template, plugin shared data, frontmatter loader) — the recent-posts fix moves to a separate PR - Address review feedback: complete the openapi.yaml snippet so the TypeScript example compiles against it, explain agent skills with the eject file layout, style the tagline as a quote, reword the caveat, and refine the hero diagram (bigger subtitle, no window dots, thinner accent-colored arrows)
The mcp-server markdownlint exclusion belongs in its own PR.
|
Thanks for the design pass! Applied: the tagline is styled as a quote, the banner subtitle is bigger, the window dots are removed, and the arrows are thinner in the accent color. The caption under the banner is gone — the post was converted to plain markdown, so the diagram is a regular image in the body now. |
|
Note for all reviewers: I reworked the PR to keep it minimal — the post is now a plain markdown blog post (3 files: the post, the diagram SVG, and 2 lines of category metadata). All theme/template changes are reverted; the recent-posts fix and moving the Blog components into this repo will come in a separate PR. The markdownlint failure is a pre-existing issue with docs/realm/customization/mcp-server/index.md on main, unrelated to this PR — it will be fixed separately. |







What/Why/How?
preview https://marketing-site--3aecf8.preview.redocly.app/blog/agent-friendly-sdks
Adds the "Open-source, agent-friendly SDKs and tooling from OpenAPI description" blog post announcing
redocly generate-client— built as a React page (blog/agent-friendly-sdks.page.tsx) instead of markdown, since the post has custom visuals: an inline SVG hero diagram, a numbered quick-start section with highlighted code, a generators table, and a reviewer-style CTA card with the eclipse glow.Supporting theme changes:
@theme/utils/blog-post.js— blog routes ending in.page.tsxload their frontmatter via Realm'sreact-frontmatterloader, so React posts appear in the blog index, category pages, and RSS feed like markdown posts.@theme/plugin.js— publishes a newblog-recent-postsshared data (top 4 posts) attached to every post route.@theme/components/Blog/RecentPosts.tsx+@theme/templates/BlogPost.tsx— the "Latest from our blog" section now excludes the currently open post (previously the newest post listed itself). The BlogPost template is localized from marketing-pages with that one behavioral change.blog/metadata/blog-metadata.yaml— new categories:api-descriptions:openapiandapi-lifecycle:sdks(existingapi-specificationsuntouched).Notes
publishedDateif publication slips./docs/cli/commands/generate-client,/docs/cli/guides/customize-client-generation) — verify they exist by publish time.imageunder a semi-transparent gradient, and recent posts follow the no-image convention.Testing
Verified with
npx @redocly/cli preview(1350 pages, no build errors): post renders with author/date/categories, appears first on /blog and in feed.xml with correct categories, recent-posts section excludes the open post on both React and markdown posts, new category pages return 200, and the layout holds at mobile widths.🤖 Generated with Claude Code