Most deep linking solutions are SaaS products with usage-based pricing. I wanted something simpler: a Go library I could drop into any project and run on my own infrastructure.
Background
This started as an internal service at work. We needed short links with OG preview pages for sharing content on social platforms, plus app store redirects for mobile users who didn’t have the app installed. The usual flow: user clicks a link, sees a preview page with Open Graph meta tags (so Slack, Twitter, Telegram render a nice card), and gets redirected to the app or a fallback URL.
It was tightly coupled to our infrastructure: internal auth middleware, specific template paths, and assumptions about the deployment environment. I decided to extract and clean it up as a standalone library that anyone could use.
What It Does
deeplink is a Go library that provides:
- Short link generation:
POST /shortenwith a JSON payload, get back a short URL - Click tracking: every visit increments a counter, queryable per link
- OG preview pages: customizable HTML templates with Open Graph meta tags for rich link previews
- Platform-aware redirects: detect iOS/Android via User-Agent and redirect to the appropriate app store
- Pluggable processors: define how different link types are handled during generation
It works in two modes: as a library you mount on your own HTTP mux, or as a standalone server with Redis storage.
Two runtime dependencies: go-nanoid for ID generation and go-redis for the Redis backend. That’s it.
Use Cases
Concretely, this is useful if you need any combination of the following.
Short links with app store redirects and preview pages. Generate short URLs, serve OG preview pages for rich cards on social platforms, and redirect mobile users to the appropriate app store. You control the data, there’s no usage limit, and it runs on your infrastructure.
Campaign link tracking. Generate short links for campaigns and track clicks per link. List all links by type to see what’s performing.
Mobile app deep linking. Serve apple-app-site-association and assetlinks.json from the /.well-known/ route. Configure store URLs and the library handles platform detection and redirects.
Any service that needs short URLs with metadata. The processor interface is generic. The built-in RedirectProcessor handles simple URL redirects, but you can implement your own for any link type.
Design Decisions
A few choices worth explaining:
Library-first, server second. The core package (deeplink) has no main function. You create a Service, register processors, and call Handler() to get an http.Handler. The standalone server in cmd/deeplink is just one way to use it. It reads config from environment variables and wires up Redis. If you already have an HTTP server, you mount the handler alongside your routes:
service, _ := deeplink.New(deeplink.Config{
BaseURL: "https://link.example.com",
Store: deeplink.NewMemoryStore(),
})
service.Register(deeplink.RedirectProcessor{})
mux := http.NewServeMux()
mux.Handle("/", service.Handler())
mux.HandleFunc("GET /hello", yourHandler)
http.ListenAndServe(":8090", mux)
Pluggable processors. Different link types need different handling. A redirect link just validates and stores a URL. A product share link might fetch metadata from an API. Instead of adding if/else branches for each type, the Processor interface lets you register handlers:
type Processor interface {
Type() string
Process(ctx context.Context, link *Link) error
}
The built-in RedirectProcessor covers the common case. For anything custom, implement the interface and register it. There’s a working example in the repo.
Two storage backends. Redis for production, in-memory for testing and development. The Store interface is small enough that adding PostgreSQL or SQLite would be straightforward:
type Store interface {
Save(ctx context.Context, id string, payload *Link) error
Get(ctx context.Context, id string) (*Link, error)
IncrClick(ctx context.Context, id string) (int64, error)
Clicks(ctx context.Context, id string) (int64, error)
List(ctx context.Context, linkType, env string, cursor uint64, count int64) ([]LinkInfo, uint64, error)
}
Quick Start
Start Redis and the server:
docker compose up -d
go run ./cmd/deeplink
Create a short link:
curl -X POST http://localhost:8090/shorten \
-H 'Content-Type: application/json' \
-d '{"type":"redirect","url":"https://example.com/docs","title":"Docs"}'
{"short_url": "http://localhost:8090/aBcDeFgHiJkLmNoPq"}
Open the short URL in a browser, you’ll see an OG preview page. Check the click count:
curl http://localhost:8090/links/redirect/aBcDeFgHiJkLmNoPq
{"short_link": "http://localhost:8090/aBcDeFgHiJkLmNoPq", "url": "https://example.com/docs", "clicks": 3}
What’s Next
This is v0.1.0. The core works but there’s room to grow:
- TTL support: links don’t expire yet
- Richer analytics: click counts are there, but referrer, geo, and device breakdowns are not
- More storage backends: PostgreSQL and SQLite are natural additions
Issues and PRs are welcome.
Links
- GitHub: github.com/yinebebt/deeplink
- Go Reference: pkg.go.dev/github.com/yinebebt/deeplink
Thanks for reading.