diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1c8e6eb25..9c74ae26b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -31,6 +31,10 @@ jobs:
run: |
npm run build
npm run build:site
+ - name: 📦 Build CLI
+ run: |
+ npm --prefix cli install
+ npm --prefix cli run build
- name: ✅ Run continuous integration tests
run: npm run ci
diff --git a/.gitignore b/.gitignore
index 403175f47..87c39da61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,13 +3,20 @@ dist
esm
lib
tsconfig.tsbuildinfo
-
+tsconfig.*.tsbuildinfo
pnpm-lock.yaml
package-lock.json
coverage
-
+.DS_Store
.claude
.docusaurus
build
.codex/skills
-.claude/skills
\ No newline at end of file
+.claude/skills
+
+# CLI package build artifacts
+cli/node_modules
+cli/esm
+cli/lib
+cli/coverage
+cli/tsconfig.tsbuildinfo
diff --git a/README.md b/README.md
index f2ee5ecdb..48eaa8175 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,47 @@ for (const chunk of chunks) {
+## Non-Browser Rendering
+
+Render infographics to SVG strings in Node.js environment (SSR, CLI tools, etc.).
+
+```ts
+import { renderToSVG } from '@antv/infographic/ssr';
+
+const result = await renderToSVG({
+ input: `
+infographic list-row-simple-horizontal-arrow
+data
+ items:
+ - label: Step 1
+ desc: Start
+ - label: Step 2
+ desc: In Progress
+ - label: Step 3
+ desc: Complete
+`,
+});
+
+console.log(result.svg);
+```
+
+### CLI Tool
+
+For command-line usage, use the dedicated CLI package:
+
+```bash
+# Install globally
+npm install -g @antv/infographic-cli
+
+# Render to file
+infographic input.txt -o output.svg
+
+# Render to stdout
+infographic input.txt
+```
+
+See [@antv/infographic-cli](https://www.npmjs.com/package/@antv/infographic-cli) for more details.
+
## 💬 Community & Communication
- Submit your questions or suggestions on GitHub
diff --git a/__tests__/unit/ssr/renderer.test.ts b/__tests__/unit/ssr/renderer.test.ts
new file mode 100644
index 000000000..1ae60714c
--- /dev/null
+++ b/__tests__/unit/ssr/renderer.test.ts
@@ -0,0 +1,193 @@
+import { describe, expect, it } from 'vitest';
+import { renderToSVG } from '../../../src/ssr';
+import { getPalette } from '../../../src/renderer/palettes';
+
+describe('SSR Renderer', () => {
+ it('should failed with unknown_key', async () => {
+ const syntax = `infograph template
+data
+ items
+ - label Step 1`;
+
+ const result = await renderToSVG({
+ input: syntax,
+ });
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors[0].code).toBe('unknown_key');
+ });
+ it('should failed with bad syntax', async () => {
+ const syntax = `infographic template
+data
+items
+ - label Step 1`;
+
+ const result = await renderToSVG({
+ input: syntax,
+ });
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors[0].code).toBe('unknown_key');
+ });
+ it('should failed with no template', async () => {
+ const syntax = `
+data
+ items
+ - label Step 1`;
+
+ const result = await renderToSVG({
+ input: syntax,
+ });
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors[0].message).toBe('No template specified');
+ });
+ it('should handle unknown template', async () => {
+ const syntax = `infographic unknown-template
+data
+ items
+ - label Step 1
+ desc Start`;
+
+ const result = await renderToSVG({
+ input: syntax,
+ });
+ expect(result.errors.length).toBeGreaterThan(0);
+ expect(result.errors.some((error) => error.message === 'No such template: unknown-template')).toBe(true);
+ });
+
+ it('should render simple syntax to SVG', async () => {
+ const result = await renderToSVG({
+ input: `infographic list-row-simple-horizontal-arrow
+data
+ items
+ - label Step 1
+ desc Start
+ - label Step 2
+ desc In Progress
+ - label Step 3
+ desc Complete`,
+ });
+ expect(result.errors).toHaveLength(0);
+ expect(result.svg).toContain('