Shipnoted

Docs

Next.js

The widget in one line, your own changelog with hooks, and customers who are known when they vote.

  1. Create a project

    Sign in, create a project, and note its slug. Your public pages are live at https://shipnoted.com/p/your-slug already.

  2. Add the widget

    The quickest way is the CLI, which edits app/layout.tsx for you and shows what it changed:

    npx shipnoted init

    Or add it yourself, once, to the root layout:

    app/layout.tsx

    import Script from "next/script"
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            {children}
            <Script src="https://shipnoted.com/widget.js" data-project="your-slug" strategy="afterInteractive" />
          </body>
        </html>
      )
    }

    A bell button appears in the corner. Press it: you have a changelog, an idea box and voting.

Your own changelog with hooks#

If you would rather draw the changelog yourself, use the headless hooks. Fetch the first page on the server so there is no loading state, and let the hook take over from there.

npm i @shipnoted/react
import { createPublicClient } from "@shipnoted/react/core"
import { Changelog } from "./changelog"

export default async function Page() {
  const client = createPublicClient({ project: "your-slug", baseUrl: "https://shipnoted.com" })
  const { releases } = await client.releases({ limit: 10 })
  return <Changelog initialData={releases} />
}

See React hooks for useFeedback (list, vote, post an idea) and the rest.

Verified customers#

Tell Shipnoted who is signed in and their votes become one verified person on every device, carry their plan and revenue, and the person is emailed when their request ships. You sign a short token on your server with the identify secret (Install & connect).

app/api/shipnoted-token/route.ts

import { SignJWT } from "jose"
import { auth } from "@/auth" // your own auth

export async function GET() {
  const user = await auth()
  if (!user) return Response.json({ token: null })
  const token = await new SignJWT({
    email: user.email, name: user.name,
    plan: user.plan, mrr: user.mrr, company: user.company,
    attrs: { beta: user.beta },
  })
    .setProtectedHeader({ alg: "HS256" })
    .setSubject(user.id)
    .setExpirationTime("1h")
    .sign(new TextEncoder().encode(process.env.SHIPNOTED_IDENTIFY_SECRET!))
  return Response.json({ token })
}

in a client component

const { token } = await (await fetch("/api/shipnoted-token")).json()
if (token) window.Shipnoted?.identify(token)   // the widget
// or <ShipnotedProvider project="your-slug" userToken={token}>  // the hooks

Ship the first release#

Write one in the dashboard, or from the terminal:

export SHIPNOTED_API_KEY=snk_…   # Install & connect → API keys
npx shipnoted release create --title "Hello" --body "- Our first release" --publish

Then have it happen on every tag with the GitHub Action.