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

@vuerend/core

v0.3.0

Published

Zero JavaScript-first Vue MPA runtime and Vite v8 plugin with SSR, SSG, and secure islands.

Downloads

1,601

Readme

@vuerend/core

Zero JavaScript-first Vue runtime and Vite v8 plugin for MPAs with SSR, SSG, explicit routes, secure islands, and opt-in runtime caching.

Goals

  • Vite v8 with the Environment API instead of a client router or filesystem routing
  • Zero JavaScript-first rendering for MPA-style apps
  • srvx-first runtime so the same fetch handler can target Node, Deno, Bun, Cloudflare Workers, dynamic workers, Vercel/Netlify edge-style runtimes, and service workers
  • Server-rendered documents by default, with explicit component-level island boundaries
  • SSR and SSG as the primary rendering modes, with ISR and revalidation when freshness needs it
  • Vue SFC and JSX support out of the box

Install

pnpm install

For Nix-based setups, a small flake.nix is included so nodejs_24 and pnpm are available in the dev shell.

Commands

vp check
vp test
vp pack

Quick Start

// vite.config.ts
import { defineConfig } from "vite";
import { vuerend } from "@vuerend/core/vite";

export default defineConfig({
  plugins: [
    vuerend({
      app: "./src/app.ts",
      islands: "./src/islands.ts",
    }),
  ],
});

The islands option is optional. Leave it out when the app is pure server components and should return no client JavaScript.

// src/app.ts
import HomeRoute from "./routes/HomeRoute";
import { defineApp, defineRoute } from "@vuerend/core";

export default defineApp({
  routes: [
    defineRoute({
      path: "/",
      component: HomeRoute,
      render: { strategy: "ssg" },
    }),
  ],
});
// src/islands.ts
import { defineIsland, defineIslands } from "@vuerend/core";

export const CounterIsland = defineIsland<{ count: number }>("counter", {
  load: () => import("./islands/CounterIslandView.loader"),
  hydrate: "visible",
});

export default defineIslands([CounterIsland]);

Rendering Model

  • Zero JavaScript is the starting point. Regular Vue components are server components by default and ship no browser JavaScript.
  • Route components stay server-only. Render defineIsland() components inside them when a narrow client boundary is needed.
  • defineIsland() marks an explicit boundary and emits a small JSON payload plus a targeted hydration root.
  • Prefer load()-only island definitions so the client entry can stay small and the island component can live in its own async chunk.
  • SSR-rendered islands can wake hydration on an early button click or form submit, then replay that first interaction after the client component mounts.
  • Vue 3.6 Vapor islands can use the vapor plugin option to hydrate through the Vapor runtime.
  • ssr: false creates a client-only island.
  • Island props must be plain JSON, and slots are intentionally rejected to keep the serialization surface small and predictable.

Vue Vapor

Vuerend has an opt-in Vapor hydration entry for Vue 3.6+ apps. Use it when the client island registry is made of SFCs authored with <script setup vapor> and you want to experiment with smaller island JavaScript:

Install the matching Vapor runtime next to Vue:

pnpm add [email protected] @vue/[email protected]
// vite.config.ts
import { defineConfig } from "vite";
import { vuerend } from "@vuerend/core/vite";

export default defineConfig({
  plugins: [
    vuerend({
      app: "./src/app.ts",
      islands: "./src/islands.ts",
      vapor: true,
    }),
  ],
});

vapor: true hydrates islands with Vue's createVaporSSRApp and keeps that runtime on a lazy path until an island actually mounts. For a gradual migration where Vapor and regular Vue components are intentionally mixed inside the same island tree, use vapor: "interop" or vapor: { mode: "interop" }. Interop is safer for mixed trees, but it can include both runtimes and reduce the bundle-size win.

Routing

  • Routing is explicit with defineRoute().
  • No filesystem router is required.
  • No client router is included.
  • The navigation model is ordinary MPA links and documents.
  • Route params come from the server request path, and pages can resolve props with getProps(context).

Document Head

  • defineApp({ document }) sets app-wide defaults for titles, meta tags, and shared assets.
  • defineRoute({ head }) accepts either a static object or a function when the head depends on the request or resolved props.
  • Use meta for SEO and Open Graph tags, and stylesheets for shared CSS files you want injected as <link rel="stylesheet">.
import HomeRoute from "./routes/HomeRoute";
import { defineApp, defineRoute } from "@vuerend/core";

export default defineApp({
  document: {
    title: "Example",
    titleTemplate: "%s | Vuerend",
    meta: [{ name: "description", content: "Shared site description" }],
    stylesheets: ["/styles/site.css"],
  },
  routes: [
    defineRoute({
      path: "/",
      component: HomeRoute,
      head: {
        title: "Home",
        meta: [
          { property: "og:title", content: "Home" },
          { property: "og:type", content: "website" },
        ],
        stylesheets: ["/styles/home.css"],
      },
    }),
  ],
});

Dynamic OG Images

  • Use defineImageRoute() when a route should return an image instead of an HTML document.
  • The image route component is still a normal server-side Vue component, so Vue SFC templates work well for OG cards.
  • createRequestHandler() needs an imageRenderer implementation. @vuerend/node ships createChromiumImageRenderer() for Playwright + Chromium.
import HomeRoute from "./routes/HomeRoute";
import OgCardRoute from "./routes/OgCardRoute.vue";
import {
  defineApp,
  defineImageRoute,
  defineRequestHandlerOptions,
  defineRoute,
} from "@vuerend/core";
import { createChromiumImageRenderer } from "@vuerend/node";

export const requestHandlerOptions = defineRequestHandlerOptions(() => ({
  imageRenderer: createChromiumImageRenderer(),
}));

export default defineApp({
  routes: [
    defineRoute({
      path: "/",
      component: HomeRoute,
      head(context) {
        const ogImageUrl = new URL("/og/home.png", context.url).href;

        return {
          meta: [{ property: "og:image", content: ogImageUrl }],
        };
      },
    }),
    defineImageRoute({
      path: "/og/home.png",
      component: OgCardRoute,
      getProps() {
        return {
          title: "Dynamic OG",
        };
      },
      head: {
        stylesheets: ["/styles/og.css"],
      },
      image: {
        width: 1200,
        height: 630,
        format: "png",
      },
    }),
  ],
});

Install Playwright in the app that renders the image routes:

pnpm add -D playwright
pnpm exec playwright install chromium

Middleware

  • defineApp({ middleware }) runs fetch-style middleware before route matching and rendering.
  • Middleware can short-circuit requests, rewrite the request passed to next(), or decorate the response after await next().
  • Use context.state to share request-scoped data with getProps(), head(), and other route hooks.
import HomeRoute from "./routes/HomeRoute";
import { defineApp, defineRoute } from "@vuerend/core";

export default defineApp({
  middleware: [
    async (request, context, next) => {
      context.state.requestPath = new URL(request.url).pathname;
      const response = await next();
      response.headers.set("x-powered-by", "vuerend");
      return response;
    },
  ],
  routes: [
    defineRoute({
      path: "/",
      component: HomeRoute,
      getProps(context) {
        return {
          requestPath: String(context.state.requestPath ?? "/"),
        };
      },
    }),
  ],
});

Client State

  • useClientState() is for hydrated islands that need shared browser state in an MPA.
  • The default storage is sessionStorage, so state survives full page navigations within the same tab while keeping SSR request-local.
  • Import it from @vuerend/core/client inside interactive islands only. Server-only routes still ship no browser JavaScript.
import { defineComponent } from "vue";
import { useClientState } from "@vuerend/core/client";

export default defineComponent({
  name: "ReadingListIsland",
  setup() {
    const count = useClientState("reading-list", 0);

    return () => (
      <button type="button" onClick={() => (count.value += 1)}>
        saved books: {count.value}
      </button>
    );
  },
});

Rendering Strategies

  • ssr: render on demand
  • ssg: render at build time and include the route in prerender output
  • isr: cache rendered HTML and revalidate based on revalidate
  • Runtime cache is opt-in. Use render.cache: true when you want HTML caching.
defineRoute({
  path: "/about",
  component: AboutPage,
  render: {
    cache: true,
    strategy: "isr",
    revalidate: 60,
  },
});

Runtime Targets

  • createRequestHandler() returns a fetch-compatible handler.
  • Use @vuerend/node, @vuerend/bun, @vuerend/deno, @vuerend/cloudflare, and @vuerend/service-worker for thin runtime adapters.
  • The server build outputs a fetch handler in dist/server/index.js and prerendered/static assets in dist/client.

Examples

  • The example suite uses a shared layout: src/app.ts, src/data/*, src/routes/*, and optional src/islands/*.
  • Start with ../../examples/README.md if you want the scenario-first index and reading order.
  • ../../examples/explicit-routes: handbook / docs hub with explicit routes, SSG pages, and social cards
  • ../../examples/secure-islands: launch site with targeted hydration and client-only signup
  • ../../examples/isr-cache: release-notes site with ISR landing page and prerendered entries
  • ../../examples/node-srvx: internal-tool Node entry powered by srvx/node
  • ../../examples/cloudflare-worker: edge-delivered status board on Cloudflare Workers
  • ../../examples/mixed-sfc-jsx: buying-guide MPA mixing Vue SFC pages and JSX islands
  • ../../examples/social-cards: dynamic OG image workflow authored as Vue SFCs