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

next-inline-editor

v0.1.3

Published

Zero-config inline content editor for Next.js sites backed by GitHub. Edit text and images directly on your live pages.

Readme

next-inline-editor

Zero-config inline content editor for Next.js sites. Edit text and images directly on your live pages. Changes commit to GitHub and deploy automatically.

No database. No hosted CMS. No separate editing UI. Your live site is the editor.


Quick start

npm install next-inline-editor
npx next-inline-editor init

The init command will:

  • Detect your project structure (App Router, TypeScript or JS)
  • Create all API routes under app/api/admin/
  • Create app/admin/page.tsx and app/admin/login/page.tsx
  • Add the required environment variables to .env.local

Then fill in two values in .env.local and you're done:

ADMIN_PASSWORD=your-secure-password-here
GITHUB_TOKEN=ghp_your-token-here   # https://github.com/settings/tokens (Contents: read & write)
GITHUB_REPO=owner/repo-name

Visit /admin/login, enter your password, and start editing.


How it works

  1. Add data-edit="field.path" to any text element in your components
  2. Add data-edit-image="field.path" to any image element
  3. At /admin, those elements become editable in place — text is clickable, images get a "Change image" button
  4. Saving commits the updated JSON directly to your GitHub repo and triggers your normal deploy pipeline

Adding data-edit attributes

After running init, wire up your components so the editor knows what to edit:

// components/MyHomePage.tsx
export default function MyHomePage({ content }) {
  return (
    <div>
      <h1 data-edit="hero.title">{content.hero.title}</h1>
      <p data-edit="hero.subtitle">{content.hero.subtitle}</p>

      <div
        data-edit-image="hero.backgroundImage"
        style={{ backgroundImage: `url(${content.hero.backgroundImage})` }}
      />

      <img
        data-edit-image="logo.src"
        src={content.logo.src}
        alt="Logo"
      />
    </div>
  );
}

Then update the generated app/admin/page.tsx to use your component:

import { AdminEditor } from 'next-inline-editor';
import homeContent from '../../content/home.json';
import MyHomePage from '../../components/MyHomePage';

export default function AdminPage() {
  return (
    <AdminEditor
      initialContent={homeContent}
      contentFile="content/home.json"
      pageLabel="Home"
    >
      {(content) => <MyHomePage content={content} />}
    </AdminEditor>
  );
}

Multiple pages

Pass a pages array to show a page-switcher dropdown in the editor toolbar:

// app/admin/page.tsx
import { AdminEditor } from 'next-inline-editor';

export default async function AdminPage({ searchParams }) {
  const page = (await searchParams).page ?? 'home';
  const { content, contentFile, pageLabel } = getPageConfig(page);

  return (
    <AdminEditor
      initialContent={content}
      contentFile={contentFile}
      pageLabel={pageLabel}
      pages={[
        { label: 'Home', href: '/admin' },
        { label: 'About', href: '/admin?page=about' },
        { label: 'Contact', href: '/admin?page=contact' },
      ]}
    >
      {(content) => <PageComponent content={content} />}
    </AdminEditor>
  );
}

Content file structure

Your JSON files can be any shape. The editor doesn't care about schema — it just reads and writes whatever you give it.

content/home.json

{
  "hero": {
    "title": "Welcome",
    "subtitle": "We build great things.",
    "backgroundImage": "/images/hero.jpg"
  },
  "about": {
    "heading": "About us",
    "body": "We are a small team..."
  }
}

Arrays work too — use numeric indexes in paths:

<div data-edit-image="slides.0.image" />
<p data-edit="slides.0.caption">{content.slides[0].caption}</p>

Image uploads

When an editor clicks "Change image", the file is:

  1. Validated (JPEG, PNG, WebP, or GIF; max 10MB)
  2. Committed to public/uploads/ in your GitHub repo
  3. The path is saved into your content JSON as /uploads/filename.jpg

The image is immediately previewed before the deploy completes.

To change the upload directory, replace the generated upload route:

// app/api/admin/upload/route.ts
import { handleUpload } from 'next-inline-editor/api/upload';

export async function POST(request: Request) {
  return handleUpload(request, {
    uploadDir: 'public/media',
    publicPrefix: '/media',
  });
}

Deploy pipeline

This package commits directly to your GitHub repo. For automatic deploys on commit, connect your repo to:

  • Vercel — auto-deploys on every push, no config needed
  • Netlify — enable "continuous deployment" in site settings
  • Cloudflare Pages — connect repo and set build command

After saving in the editor, the live site updates in ~1 minute.


Environment variables

| Variable | Required | Description | |---|---|---| | ADMIN_PASSWORD | Yes | Password for the login page | | ADMIN_SESSION_SECRET | Yes | Secret for signing session tokens — auto-generated by init | | GITHUB_TOKEN | Yes | GitHub personal access token with Contents read & write scope | | GITHUB_REPO | Yes | owner/repo format | | GITHUB_BRANCH | No | Branch to commit to (default: main) |


Security notes

  • Session cookie is httpOnly, secure (in production), and sameSite: lax
  • Password and token comparisons use timing-safe equality to prevent timing attacks
  • The save route validates file paths against your explicit ALLOWED_FILES whitelist — arbitrary file writes are not possible
  • The /admin page has no server-side auth guard — the API routes are always protected, but add Next.js middleware if you want to block unauthenticated page access entirely

TypeScript

The AdminEditor children prop passes a loosely typed content object. Cast it to your own type for full safety:

import type { HomeContent } from '../types/content';

<AdminEditor initialContent={homeContent} ...>
  {(content) => <MyHomePage content={content as unknown as HomeContent} />}
</AdminEditor>