skim
Preview how a link looks when shared on social media — including localhost — without deploying.
skim extracts OpenGraph / Twitter / standard meta tags from any URL and renders realistic preview cards for Facebook, Twitter/X, LinkedIn, Discord, and Slack, plus validation diagnostics and a raw meta-tag grid.
The cards are approximations of how each platform renders a shared link — not pixel-perfect reproductions. Exact appearance varies by platform, client, and device, and shifts over time as the platforms change. Use skim to sanity-check your tags and layout, not to match a specific platform down to the pixel.
Install
With Go installed (1.26+):
go install github.com/Seismix/skim@latest
The UI ships prebuilt and embedded, so there's no Node toolchain to install. This drops a skim binary in your $GOBIN (usually ~/go/bin, or %USERPROFILE%\go\bin on Windows) — make sure that's on your PATH.
Quick start
Run it:
skim
It starts a local server on a free port and opens your default browser. To pre-fill and auto-preview a URL:
skim http://localhost:3000
(Building from source instead? The binary lands at ./dist/skim — see Building.)
You can type a bare host — reddit.com becomes https://reddit.com, while localhost:3000 (and 127.0.0.1, *.localhost) becomes http://….
Flags: --port N (fixed port), --no-open (don't launch the browser), --user-agent "…". skim binds loopback (127.0.0.1) only — it's a personal local tool. Press Ctrl+C to stop — the listener closes and the port frees.
By default skim sends the OpenGraph crawler User-Agent (facebookexternalhit), since many sites serve share metadata only to recognized crawlers — Reddit, for example, returns an anti-bot page otherwise. This makes skim see what Facebook / Discord / Slack see. Override with --user-agent.
Why a local server at all?
The localhost feature dictates the architecture. A browser page can't fetch() an arbitrary origin's raw HTML (CORS + mixed content), so skim does the fetch on your machine and hands the parsed result to the UI. That fetch resolving to your localhost is why skim runs locally instead of as a hosted site.
skim binary
├── GET / → serves the embedded Svelte UI (web/dist)
├── POST /api/fetch-og → fetches the URL on THIS machine, parses meta, returns JSON
└── GET /api/img → proxies a preview image same-origin, so browser tracking
protection / hotlink checks don't block it
Building
Building needs Go and pnpm (used only to build the UI; the shipped binary has no runtime deps).
make # build the Svelte UI, then the native binary → dist/skim
make dist # build the UI, then cross-compile static binaries for win/mac/linux → dist/
make web # build just the Svelte UI → web/dist
make run ARGS="http://localhost:3000" # build, then run the binary serving the built UI
The Svelte UI is compiled by Vite to static assets and embedded into the binary via go:embed, so distribution is still a single file. Cross-compilation needs no extra setup — Go's GOOS/GOARCH plus CGO_ENABLED=0 produce fully static binaries.
Developing the UI
For live frontend work with hot-reload:
make dev # or: cd web && pnpm dev
This starts the Vite dev server (HMR for web/src/**) and automatically builds and runs the Go backend, proxying /api to it, so the UI is fully functional while you edit. Vite prints the local URL (e.g. http://localhost:5173); Ctrl+C stops both and frees the backend port. Only the UI hot-reloads — main.go changes need a dev-server restart.
Tech stack
| Layer |
Technology |
| Server + CLI |
Go (net/http, embed) |
| HTML parsing |
goquery |
| UI |
Svelte 5 + Vite + Tailwind CSS, built to static assets and embedded; Milk Interactive branding |