npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nuxt-panes

v0.1.1

Published

Retained-page tabs for Nuxt. Real history, mounted panes, Chrome-like strip.

Readme

nuxt-panes

Retained-page tabs for Nuxt. One router, several mounted page instances, Back and Forward are real browser history.

SPA apps keep asking for Google Chrome-style tabs: leave the page running when you switch away, come back to the same scroll and the same form. Most implementations fake that with KeepAlive and then fight the browser. This module does not. Each tab is a real NuxtPage. Each visit still pushes a real history entry. Closing a tab does not splice history.

Nuxt 3+. Client-side after mount. The first SSR paint is a normal NuxtPage.

Docs

| Doc | What it covers | |---|---| | How it works | Panes, history stamps, Back/Forward, close, persistence | | Configuration | Options, rules, globs, precedence | | API | Components, composables, page meta |

Install

pnpm add nuxt-panes
export default defineNuxtConfig({
  modules: ['nuxt-panes']
})

configKey is panes.

Local checkout before npm exists:

pnpm add nuxt-panes@file:../nuxt-panes

Build the package first (pnpm prepack in this repo) so dist/ exists.

Quick start

1. Replace the root NuxtPage with PaneHost inside the layout. Sidebar and navbar stay shared. Only the page body is tabbed.

<!-- app.vue -->
<template>
  <NuxtLayout>
    <PaneHost />
  </NuxtLayout>
</template>

2. Put the tab strip in that layout, next to the navbar, not inside a page.

<!-- layouts/default.vue -->
<template>
  <header>
    <PaneTabs />
  </header>
  <slot />
</template>

3. Turn the module off for routes that should not get a pane. Login and marketing pages belong here.

export default defineNuxtConfig({
  modules: ['nuxt-panes'],
  panes: {
    exclude: ['/', '/login'],
    homePath: '/app'
  },
  routeRules: {
    '/login': { panes: false }
  }
})

4. Hidden panes stay mounted. Gate GraphQL, websockets, and timers with usePaneEnabled() so a background tab does not keep the network busy.

const enabled = usePaneEnabled()
const { result } = useQuery(MyDocument, null, { enabled })

5. On logout, throw the session away. The module has no auth hook.

const { clear } = usePanes()
await clear()

Default behavior

Each new pathname opens a tab. Visiting a path that already has a tab focuses it.

That is panes.default: {}. Override per path with module rules, Nitro routeRules, or definePageMeta({ panes }). Page meta beats routeRules beats module rules. exclude and layout: false beat all of those. Details in configuration.

// One "settings" tab no matter which settings URL you are on.
definePageMeta({
  panes: { key: 'settings' }
})

// This page is never a pane.
definePageMeta({
  panes: false
})

Stay vs new pane on a single click (works on NuxtLink, UButton :to, any :to):

<NuxtLink :to="path">Open as tab</NuxtLink>
<NuxtLink :to="paneIn(path)">Stay in this tab</NuxtLink>
<NuxtLink :to="paneTab(path)">Ignore reuse, dest tab</NuxtLink>

paneIn('/x') is /x?tab=in. The plugin reads it, then replaces the URL so the query is gone. Default unmarked links still open a new pane. Page-level reuse globs still exist for hubs where every matching dest should stay.

Tab labels come from definePageMeta({ title }), else the last path segment. Rename the active tab at runtime with usePanes().setCurrentLabel('Invoice 1842').

What close does, and what it does not

Clicking X drops the pane from the strip and replaces the URL with a neighbor (or homePath if that was the last tab). It does not delete history entries.

Back after close can land on a URL whose pane is gone. Reconcile opens that path again. That is the point of using real history: the browser stack stays honest. If you wanted close to erase Back, you would be fighting history.

Persistence

On by default. Panes write to sessionStorage under panes.v1.

Reload in the same browser tab restores the strip. Close that browser tab, or call clear(), and it is gone. This is not localStorage and not the server.

panes: {
  persist: false,            // opt out
  storageKey: 'panes.v1' // rename if you share origin with another app
}

Tabs UI

PaneTabs shrinks like Google Chrome's tab bar: equal flex, 168px cap, 46px floor, ellipsis, close chip on hover. Close icon is inline SVG. Color classes (bg-primary, text-inverted, …) come from the host theme (Nuxt UI tokens work). Drive your own strip from usePanes() if those classes are missing.

Limits (read these)

  • Pathname only. Query and hash are stripped. /inbox and /inbox?filter=unread are the same tab. Restoring a tab navigates to the stored pathname, so the query is lost.
  • Cap. maxPanes (default 12) LRU-drops closable tabs. Pinned keys and the tab you just opened are kept.
  • SSR. Panes start after PaneHost mounts. Until then you get a plain NuxtPage.
  • No auth. Call clear() yourself.
  • Background work. Hidden panes are mounted. You must gate live work. See how it works.

File map

src/
├── module.ts                # defineNuxtModule, writes #build/panes.config.mjs
└── runtime/
    ├── plugin.client.ts     # beforeEach intent + popstate
    ├── options.ts
    ├── policy.ts            # pure classify / globs / eviction
    ├── query.ts
    ├── types.d.ts           # PageMeta.panes
    ├── nitro.d.ts           # routeRules.panes
    ├── composables/
    └── components/
docs/
test/                        # node vitest
playground/

Policy is framework-free and unit-tested from test/policy.test.ts.

Publish

pnpm dev:prepare
pnpm test
pnpm prepack          # writes dist/
npm publish           # or pnpm release (changelogen + tag + push)

files publishes dist only. npm pack includes README and LICENSE automatically.

License

MIT. See LICENSE.