惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
博客园 - 【当耐特】
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
大猫的无限游戏
大猫的无限游戏
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
F
Fortinet All Blogs
Y
Y Combinator Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks
Engineering at Meta
Engineering at Meta
MyScale Blog
MyScale Blog
B
Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to render chess diagrams in Node.js (no DOM, no canva...
Khatai Huseynzada · 2026-06-29 · via DEV Community

If you've ever needed to generate a chess diagram programmatically — for a blog, a PDF report, or a static site — you've probably hit the same wall I did.

Most solutions require either a browser environment, a headless Chromium instance, or a canvas polyfill that pulls in half of npm. For something as simple as "turn a FEN string into an image," that's a lot of overhead.

So I extracted the rendering core from my chess editor into a standalone package: @chessvision-org/chess-vision. Zero dependencies. Works in Node.js, Deno, Bun, and the browser. No DOM, no canvas, no network calls.

The basics

npm install @chessvision-org/chess-vision

import { generateDiagram } from '@chessvision-org/chess-vision';

const svg = generateDiagram({
  fen: 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',
  size: 400,
  showCoords: true,
});

svg is a self-contained SVG string — inline it in HTML, write it to a file, embed it in a PDF. Pieces are inlined as SVG paths (CBurnett / Lichess style), so no external resources are needed.

generateDiagram throws if the FEN string is invalid. Validate first if you're working with untrusted input:

import { generateDiagram, validateFEN, getFENValidationError } from '@chessvision-org/chess-vision';

if (!validateFEN(fen)) {
  console.error(getFENValidationError(fen)); // e.g. 'Board must have 8 ranks'
} else {
  const svg = generateDiagram({ fen, size: 400 });
}

Real use cases

Static site or blog

If you write about chess and want diagrams in your posts, generate them at build time:

import { writeFileSync } from 'fs';
import { generateDiagram } from '@chessvision-org/chess-vision';

const positions = [
  {
    fen: 'r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4',
    file: 'italian.svg',
  },
  {
    fen: 'rnbqkb1r/pp3ppp/3ppn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6',
    file: 'sicilian.svg',
  },
];

for (const { fen, file } of positions) {
  writeFileSync(`./diagrams/${file}`, generateDiagram({ fen, size: 480, showCoords: true }));
}

The output SVGs embed directly in Markdown, MDX, or HTML with no image hosting needed.

Server-side rendering

Generate diagrams on request without spinning up a browser:

import express from 'express';
import { generateDiagram, validateFEN } from '@chessvision-org/chess-vision';

const app = express();

app.get('/diagram', (req, res) => {
  const fen = String(req.query.fen ?? '');
  if (!validateFEN(fen)) return res.status(400).send('Invalid FEN');

  const svg = generateDiagram({ fen, size: 400, showCoords: true });
  res.setHeader('Content-Type', 'image/svg+xml');
  res.send(svg);
});

Astro / Next.js at build time

// In an Astro component or Next.js getStaticProps
import { generateDiagram } from '@chessvision-org/chess-vision';

const svg = generateDiagram({
  fen: 'r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3',
  size: 360,
  showCoords: true,
  flipped: false,
});

// Render directly — no img tag, no src, no upload
// <Fragment set:html={svg} />   ← Astro
// <div dangerouslySetInnerHTML={{ __html: svg }} />  ← React (SVG is from your own code, not user input)

Themes and customization

The package ships 20 built-in board themes (classic, ocean, wood, forest, navy, marble, and more):

import { generateDiagram, getBoardTheme, listThemeIds, themeCoordinateColor } from '@chessvision-org/chess-vision';

listThemeIds();
// → ['classic', 'brown', 'wood', 'sand', 'slate', 'marble', 'blue', 'ocean',
//    'green', 'forest', 'mint', 'purple', 'lavender', 'red', 'coral',
//    'sunset', 'pink', 'burgundy', 'navy', 'ice']

const theme = getBoardTheme('ocean')!;
// → { name: 'Ocean', light: '#c9e4f5', dark: '#4a90a4' }

const svg = generateDiagram({
  fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
  size: 400,
  lightSquare: theme.light,
  darkSquare: theme.dark,
  showCoords: true,
  coordColor: themeCoordinateColor(theme), // → 'white' or 'black', whichever is more legible
});

Or pass any hex colors directly:

const svg = generateDiagram({
  fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
  size: 400,
  lightSquare: '#e8f4f8',
  darkSquare: '#7eb8da',
  flipped: true,       // Black's perspective
  showFrame: true,     // thin outer frame
  coordColor: 'white', // coordinate label color
  label: 'Starting position', // aria-label on the SVG
});

FEN utilities

The package also ships a full FEN parser if you need to work with positions directly:

import {
  parseFEN,
  boardToFEN,
  movePiece,
  getPieceAt,
  countPieces,
  materialBalance,
  findKing,
  STARTING_FEN,
} from '@chessvision-org/chess-vision';

// Parse to an 8×8 matrix (row 0 = rank 8, row 7 = rank 1)
const board = parseFEN(STARTING_FEN);
board[7][4]; // → 'K'  (white king on e1)

getPieceAt(board, 'e1'); // → 'K'
getPieceAt(board, 'd8'); // → 'q'

// Move a piece — pure, returns a new board, never mutates
const next = movePiece(board, 'e2', 'e4');
boardToFEN(next); // → updated piece placement string

countPieces(board);    // → { P: 8, N: 2, B: 2, R: 2, Q: 1, K: 1, p: 8, … }
materialBalance(board); // → 0  (equal material)
findKing(board, 'w');  // → 'e1'

Full FEN record

Parse and serialize all six FEN fields:

import { parseFENRecord, buildFENRecord, toggleActiveColor } from '@chessvision-org/chess-vision';

const record = parseFENRecord('rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12');
// → { board, activeColor: 'b', castling: 'KQkq', enPassant: 'e3', halfmove: 5, fullmove: 12 }

buildFENRecord(record);
// → 'rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12'

toggleActiveColor(record).activeColor; // → 'w'

Why SVG

  • Scales to any resolution without quality loss
  • Self-contained — 23 piece sets inlined as paths, no external resources
  • Embeds directly in HTML and most PDF libraries
  • Converts to raster easily with sharp if you need PNG

If you need high-res PNG for print, the web app (chessvision.org) handles that — up to 1200 DPI with accurate physical dimensions in centimeters.

Links

Open to feedback on the API — if something feels awkward to use or there's a use case I haven't covered, I'd like to know.