A shortcut-driven browser start page. Every link has a single-key shortcut, links are grouped into four boxes, and boxes are grouped into tabs.
Everything you see comes from ~/.config/startpage/config.json, read fresh on every page load, so changing your links is a matter of editing that file — or using the settings window — and hitting refresh. Nothing is sent anywhere: no account, no database, no telemetry. The first run writes a sample config for you.
git clone https://github.com/chedieck/startpage.git
cd startpage
npm install
npm run devOpen http://localhost:5173. For daily use you probably want it running in the background — see Installing as a service.
The page listens for bare keypresses — no Ctrl, no Alt:
| Key | Does |
|---|---|
Tab |
Next tab |
Shift+Tab |
Previous tab |
| any other key | Opens the link that claims that key in the current tab |
Esc |
Closes the settings window |
Tab cycling lives on Tab so that every printable key stays yours to assign.
Shortcuts are case-sensitive (g and G are two different links) and only have to be unique within a tab, so c can be Calendar on one tab and Client on another.
An empty shortcut (or -) lists the item without binding a key.
If two links in the same tab claim the same key, the first wins and the other is drawn without a key; the settings window highlights both.
Links open in the same tab, replacing the start page.
Tick Open links in a new tab in Settings → General, or set "openInNewTab": true, if you would rather keep it open.
Click the Settings icon in the bottom-right corner of the sidebar photo, or edit ~/.config/startpage/config.json directly:
{
"title": "My Startpage",
"quote": "Act as if what you do makes a difference. It does.",
"openInNewTab": false,
"quickAccess": {
"title": "Quick Access",
"icon": "🐎",
"items": [{ "url": "https://mail.google.com", "shortcut": "m", "name": "📧 Mail" }]
},
"tabs": [
{
"title": "Work",
"icon": "💼",
"sections": [
{
"title": "Projects",
"icon": "🐙",
"items": [{ "url": "https://github.com", "shortcut": "p", "name": "🐙 Projects" }]
}
]
}
],
"backgroundImage": "/api/images/background.jpg",
"frameContentImage": "/api/images/frameContent.png"
}| Field | Meaning |
|---|---|
title |
Text in the window's title bar |
quote |
The one-liner in the light blue strip |
quickAccess |
A section pinned to the bottom-right box of every tab |
tabs |
The tabs, in display order |
tabs[].sections |
Up to three sections per tab; see the layout below |
backgroundImage |
Wallpaper behind the window (optional) |
frameContentImage |
Photo in the window's left sidebar (optional) |
openInNewTab |
Open links in a new tab instead of navigating away |
Each item is { "name": ..., "url": ..., "shortcut": ... }.
The name is displayed as-is, so leading emoji or Nerd Font glyphs are just part of the name.
Set STARTPAGE_CONFIG to put the config somewhere else. Uploaded images always live in an images/ directory next to it.
Every tab is a 2×2 grid: the first three boxes are the tab's own sections, the fourth is always Quick Access.
┌──────────────┬──────────────┐
│ sections[0] │ sections[1] │
├──────────────┼──────────────┤
│ sections[2] │ quickAccess │
└──────────────┴──────────────┘
A section you leave out renders as empty space. A list too long to fit continues in a second column inside the same box, and shrinks its text if even that is not enough. The whole window scales as one unit, so browser zoom changes how big it looks and never how it is laid out.
The wallpaper and the sidebar photo default to static/background.png and static/window-content.png.
To use your own, open Settings → Customization and pick a file: it is copied to ~/.config/startpage/images/ and referenced from your config, and Use default removes the override.
By hand, set backgroundImage / frameContentImage to any path or URL the browser can load.
Run the app somewhere stable (below), then point your browser at it.
Firefox — Settings → Home → Homepage and new windows → Custom URLs → http://localhost:51991.
Chrome / Chromium — Settings → On startup → Open a specific page → http://localhost:51991.
Neither browser lets you replace the new-tab page without an extension.
make nginx puts the page on http://startpage.local so you never type a port again; it needs nginx and asks for sudo. make nginx DOMAIN=home.local picks another name, make nginx-uninstall undoes it.
The Makefile installs the built app into /opt/startpage and registers a user systemd unit, so it starts with your session and needs no root beyond writing to the prefix:
make install # build, copy to /opt/startpage, install the unit
make enable start # run it now and on every login
make status # check on it
make update # rebuild and restart after pulling changes
make nginx # optional: serve it at http://startpage.local
make uninstall # remove all of itDefaults are PREFIX=/opt/startpage, PORT=51991 and HOST=127.0.0.1; override them on the command line, e.g. make install PORT=8080.
HOST=127.0.0.1 deliberately keeps the page off your network — it is a page of your personal links, and its config endpoint is unauthenticated, so do not expose it to anything you do not trust.
npm run dev # dev server with hot reload
npm run build # production build into build/
npm run preview # serve that build
npm run check # svelte-check / TypeScript
npm run lint # prettier + eslint
npm run format # rewrite files with prettierPoint the app at a throwaway config while hacking on it:
STARTPAGE_CONFIG=/tmp/startpage.json npm run dev| Path | What |
|---|---|
src/routes/+page.svelte |
The page itself: layout, keybinds, styling |
src/lib/components/ConfigEditor.svelte |
The settings window |
src/lib/types.ts |
Config shape and the tab → grid mapping |
src/lib/server/config.ts |
Reading/writing the config file |
src/lib/default-config.json |
What a fresh install gets |
resources/ |
systemd unit and nginx snippets |
The interface font is VT323 and the list font is JetBrains Mono Nerd Font, both under the SIL Open Font License; their license files sit next to them in static/fonts/.
Copyright (C) 2026 chedieck. Licensed under AGPL-3.0-or-later: if you run a modified version of this as a service that other people can reach, they are entitled to its source.
