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

@200stack/static-forms-next

v0.2.4

Published

Next.js Static Forms adapter for 200stack build-time materialization.

Readme

Static Forms Next adapter

checkboxes(複数選択)は 0.2.3 以上(core は 0.2.2 以上)・build protocol v2 が必要です。 電話番号 tel・URL url・日付 date・数値 number は 0.2.4 以上(core も 0.2.4 以上)・build protocol v2 が必要です。

App Router の StaticForm と、Pages Router の getStaticFormProps / StaticFormFromProps でフォームを表示します。本番は output: 'export' とビルド用の manifest・snapshot が必要です。

next.config.mjs には静的出力と adapter を設定してください。

// next.config.mjs
import { createRequire } from 'node:module'

const require = createRequire(import.meta.url)

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  adapterPath: require.resolve('@200stack/static-forms-next/adapter'),
}

export default nextConfig

v2 と開発表示は slotId だけで描画できます。slotId は、200stack のフォームの画面の「サイトに貼るコード」に入っている名前(コードに書く名前)です。

// App Router
import { StaticForm } from '@200stack/static-forms-next'

export default async function ContactPage() {
  return <main>{await StaticForm({ slotId: 'slot.contact' })}</main>
}
// Pages Router
import type { InferGetStaticPropsType } from 'next'
import { StaticFormFromProps } from '@200stack/static-forms-next/react'

export async function getStaticProps() {
  const { getStaticFormProps } = await import('@200stack/static-forms-next')
  return { props: await getStaticFormProps({ slotId: 'slot.contact' }) }
}

export default function ContactPage(props: InferGetStaticPropsType<typeof getStaticProps>) {
  return <StaticFormFromProps {...props} />
}

v2 の getStaticFormProps は { staticForm: { buildProtocolVersion: 2, html, semanticDomDigest, instanceId, publicFormId, publicApiOrigin, configVersion, configDigest, publicIdGeneration, definitionDigest, hydrationOwner } } を返します。html はサーバーで生成した必須の文字列で、renderTree・style・lang は props に含みません。App Router の StaticForm も同じ props を client component に渡します。ブラウザでは受け取った HTML に controller を接続します。

これは 0.2.0 で予定する公開 API の破壊的変更です。v2 の props を手作り・保存・加工している場合は、同じ版の getStaticFormProps で作り直してください。StaticFormRenderProps は buildProtocolVersion で分かれる union で、v2 の側は StaticFormV2RenderProps として import できます。App Router では、html は SSR の HTML に加えて RSC の payload にも含まれます(以前の renderTree の代わり)。v1 (buildProtocolVersion が未指定または 1) は従来の renderTree と React 描画を維持し、開発表示は { mode: 'development', html } のままです。Pages Router の表示用 StaticFormFromProps は引き続き /react から import してください。

同じフォーム(コードに書く名前が同じもの)を1ページに複数置く場合は、2つ目以降に index: 1、index: 2 のように異なる値を渡してください。既定は 0 です。同じ instanceId の2つ目以降は診断を表示し、送信の controller を起動しません。生成される ID は固定の CSS セレクタとして扱わないでください。

v2 と開発表示の言語はフォームの定義の lang (ja・en) に従います。言語ごとに別のフォームを作り、それぞれのページに置いてください。App Router の StaticForm と Pages Router の getStaticFormProps は通常 lang の指定が不要です。定義に lang がないフォームだけ、adapter の lang: 'en' などで既定の文言を選べます。どちらも省略なら日本語です。定義と異なる lang を指定すると、本番ビルド・開発表示ともエラーになります。同じ言語の明示指定は省略と同じ結果です。

送信・戻る・確認の送信・noscript・状態・項目エラーと開発の案内は、解決した言語の辞書を使います。保存された項目名や submitLabel・backLabel などの文言は自動翻訳しません。backLabel は確認の mode: 'configured' で省略可能です。本番・開発のフォームルートには HTML の lang と既存の data-200stack-static-form-lang を出します。ページ全体の <html lang> はサイト側で指定してください。データ取得に失敗して定義が分からない開発診断は、adapter の lang、省略なら日本語を使います。v1 の出力は従来の英語固定で、定義・adapter の lang を適用しません。StaticFormLang 型を adapter から import できます。

0.1.0 は 0.2.0 のビルド入力を読めません。新しい定義・入力を使うサイトは adapter と core・SDK を対応する版へ更新してください。

言語を足す手順

  1. core の src/core/messages.ts にフォーム文言・状態・検証エラー・開発診断をすべて持つ辞書を足し、staticFormLanguageNames に表示名を足します。StaticFormLang と wire schema の許可値は辞書のキーから導かれます。実行時の登録はしません。
  2. 管理画面の自動返信の固定文(件名・本文・HTML の lang とフォント)と、作成時の既定定義・言語選択を足します。
  3. core の dist と controller bundle、Worker の同梱 controllers、adapter の controller contract、agent の生成物を作り直し、browser import 境界・bundle 予算・core と adapters のテストを確認します。言語追加には controller の再配備も必要です。
  4. DB(管理画面) → Worker(新しい core と controller) → build-runtime → 管理画面 → npm の順で配備し、サイトを再ビルドします。

開発表示の重複診断には CSS の :has() を使い、JS は実行しません。:has() と埋め込みの <style> が使えるブラウザ・CSP が必要です。

route・outputPath は v1 入力でのみ必須です。v2 では使いませんが、渡された値は検証します。

見た目を指定する(v2)

import { type StaticFormStyle } from '@200stack/static-forms-next'

const style = {
  root: 'mx-auto max-w-xl',
  section: { title: 'text-xl font-bold' },
  field: { root: 'mb-5', label: 'font-medium', error: 'text-rose-600' },
  email: { control: 'rounded border aria-[invalid=true]:border-rose-600' },
  radio: { group: 'grid gap-2', option: 'flex gap-2', input: 'size-4' },
  byName: { email: { label: 'text-indigo-800' } },
} satisfies StaticFormStyle

値はクラス名の文字列だけです。項目は field → 種類 (text / email / textarea / select / radio / checkbox) → byName[name] の順で連結します。この順は CSS の優先順位を保証しません。byName の未知の name は警告になり、項目種類に合わない部品も警告して使いません。

style と lang はサーバーで HTML に反映し、v2 の client props には含めません。remount の key は HTML の SHA-256 である semanticDomDigest です。HTML が変わると remount して入力・フォーカス・送信状態をリセットし、同じ digest の再描画では保持します。エラーの入力は aria-invalid="true"、項目外枠は data-200stack-static-form-invalid を目印にできます。送信中は root に aria-busy="true" が付き、送信・確認ボタンは disabled になります。

| キー | DOM (part) | | --- | --- | | root / form | ルートの section / 内側の form (root / form) | | section.root / .title / .description | section / h2 / p | | field.root、*.label / .description / .error | 項目の div、label または legend、説明 p、エラー p | | text・email・textarea・select の .control | input / textarea / select | | radio・checkbox の .group / .option / .input | fieldset / 選択肢の label / input | | formErrors / status / submit | 全体エラー / 状態 / 主送信 button | | privacy.root / .link | 案内 p / a | | confirmation.root / .title / .description / .list / .item / .label / .value / .back / .send | 確認画面の div / h2 / p / dl / div / dt / dd / buttons | | completion.root / .title / .body | 完了画面の div / h2 / p |

完全な公開対応は次のとおりです。

| style のキー | part の正確な値 | 本番の DOM | 開発の表示 | | --- | --- | --- | --- | | root / form | root / form | section / form | 外側の div[role=form] (root form) | | section.* | section / section-title / section-description | section / h2 / p | 同じ | | field.root / *.label / .description / .error | field / label / description / error | 項目 div / label または legend / p / p | 同じ | | text 系 .control | control | input / textarea / select | 同じ | | choice .group / .option / .input | group / option / input | fieldset / label / input | 同じ | | formErrors / status / submit | form-errors / status / submit | div / div / button | 出ない / 出ない / 無効の button | | privacy.* | privacy / privacy-link | p / a | 同じ | | confirmation.* | confirmation、confirmation-title、confirmation-description、confirmation-list、confirmation-item、confirmation-label、confirmation-value、confirmation-back、confirmation-send | div / h2 / p / dl / div / dt / dd / buttons | 出ない | | completion.* | completion / completion-title / completion-body | div / h2 / p | 出ない |

条件で DOM が出ない説明・確認・完了などは、対応する指定を書いても効果がありません。consentText が無い checkbox には group と label がありません。theme と公開目印は v2 と開発表示だけで、v1 の出力には効きません。checkbox は同意文があれば fieldset > legend + label > input、select は先頭に空の選択肢を出します。確認画面は configured・generated とも入力内容を表示します。条件では未選択 select の field_not_equals が真になるため、選択済みだけを求める場合は and(field_present, field_not_equals) を使います。制御文字、JSON 全体 32 KiB、合成済み class 属性 2,000 バイトを超える指定はビルドエラーです。開発表示も同じ検査・クラス指定を使います。

Tailwind は完全なクラス名をファイルに書き、v3 は content、v4 は @source にそのファイルを含めます。動的な bg-${color}-500 は検出されません。エラー用には aria-[invalid=true]:border-rose-600 のように書きます。

CSS の目印

v2 本番と開発表示では、style の各部品に data-200stack-static-form-part が付きます。複数の役割を持つ外側の開発表示では root form のように空白区切りなので、[data-200stack-static-form-part~="form"] と ~= を使います。

| 公開の目印 | 場所・値 | | --- | --- | | data-200stack-static-form-part | 表の部品名 | | data-200stack-static-form-type | 項目の外枠、種類 | | data-200stack-static-form-name | 項目の外枠、name | | data-200stack-static-form-required / -invalid | 必須 / エラーの項目外枠 |

name には .・: が入り得るため、[data-200stack-static-form-name="customer.email"] のように必ず引用符で囲みます。管理画面で name を変えると name を狙う CSS と byName は黙って外れます。既存の controller 用属性、controller 実行時の要素、開発表示の説明文は公開の約束ではありません。追加量の目安は項目ごとに 100〜200 バイト、選択肢ごとに約 70 バイトです(Next では HTML が client props にも載ります)。

theme.css と CSP

app/layout.tsx または pages/_app.tsx で import '@200stack/static-forms-next/theme.css' を読み込みます。色・角丸は --200stack-sf-accent、--200stack-sf-accent-hover、--200stack-sf-text、--200stack-sf-muted、--200stack-sf-border、--200stack-sf-frame-border、--200stack-sf-error、--200stack-sf-error-text、--200stack-sf-radius、--200stack-sf-surface、--200stack-sf-error-surface、--200stack-sf-on-accent、--200stack-sf-completion-surface、--200stack-sf-completion-text をルート目印で上書きできます。

@layer static-forms;
[data-200stack-static-form-part~="root"] { --200stack-sf-accent: #0f766e; --200stack-sf-surface: #06251f; --200stack-sf-text: #ecfdf5; --200stack-sf-border: #2d6a5c; }

一般のサイトでは CSS の先頭で @layer static-forms; を宣言します。レイヤー外の CSS と、後ろにあるレイヤーの CSS が勝ちます。Tailwind v4 では @layer theme, base, static-forms, components, utilities; を @import "tailwindcss"; より前に置き、@import '@200stack/static-forms-next/theme.css'; と layer() なしで読み込みます。

theme.css だけの本番は外部 CSS なので style-src 'self'(または配信元)で足りますが、v2 は常に固定の <style> も出します。本番には 'sha256-LE3mSYpIJxrT67iqOfLXzAcqron3YCrRA+rogsSVPYA='、開発表示には 'sha256-PooA9aoFhckRsEL2CK2Uxp0tODRwW2JydE6jQ+WgQ+M=' を style-src(指定時は style-src-elem にも)へ追加してください。開発表示の重複診断 style は instanceId ごとに変わるため、その出力の hash も必要です。クラス指定を使う本番でも固定 style の hash は必要です。

0.1 からの移行

| 0.1 | 0.2 | | --- | --- | | 平たい label / description / error | field.label / .description / .error | | 平たい input / textarea / select | text / email / textarea / select の .control | | fields.* / types.* の input | text 系は .control、radio・checkbox は .input | | 文字列の field / section / confirmation / completion | それぞれの .root | | choiceGroup / choiceLabel / radio / checkbox | radio / checkbox の .group / .option / .input | | sectionTitle / sectionDescription | section.title / .description | | confirmationList など | confirmation.list など | | types / fields | 種類キー / byName | | first / last | section 内の表示位置は CSS、特定項目は byName |

削除済みの StaticFormElementClasses・StaticFormFieldClasses の代わりに StaticFormFieldStyle を使います。旧キーは読み替えず、たとえば Static Forms style uses 0.1 key at fields.email; see README: Migrating from 0.1 としてエラーにします。

送信の完了と計測

完了時はメッセージを表示するか、サイトのルートからのパス(例 /contact/thanks?from=contact)へ移動します。移動先は 2,048 字以内で、/ から始まる必要があります。//、絶対 URL、バックスラッシュ、空白・制御文字(0x00〜0x20、0x7f〜0x9f、U+2028・U+2029)、#、パスの .. 区間、エンコードされた .・/・バックスラッシュ(%2e・%2f・%5c、大文字も含む)は拒否します。クエリは使えます。移動先はページの URL を基準に解決し、同じ origin であることを確認します。

サンクスページはサイト側で用意してください。末尾のスラッシュはサイトのルーティングに合わせ、検索に載せたくない場合は noindex を設定してください。そのページの計測タグは、ページを直接開いた場合にも動きます。移動中は「移動しています…」(en: Redirecting…)を表示し、送信ボタンは無効のままです。移動の呼び出しが失敗した場合は、送信失敗とは区別した案内と手動リンクを表示します。location.assign を使うため、戻るボタンで入力済みの完了状態に戻れます(bfcache で保持された場合)。

受付が確認できた時点で、ルートから 200stack-static-form:accepted を通知します。bubbles: true・cancelable: false の CustomEvent です。message では完了要素を表示した後、redirect では移動を呼ぶ前に通知します。直接受付とポーリングの両方が対象です。確認できずにポーリングが時間切れになった場合や dispose 後には通知しません。

detail は通知ごとに作る凍結した { slotId?: string, publicFormId: string, instanceId: string } です。入力値・受付 ID・receipt・session は含みません。v2 の HTML には data-200stack-static-form-slot があり、SDK が渡した slotId を通知できます。属性がない旧い HTML では slotId のキーを省略します。v1 と開発表示にはこの属性を出しません。回数の保証は「同じ controller の同じ送信で最大 1 回」です。再読み込みや remount をまたぐ 1 回だけの保証はありません。イベントは偽造できるので、課金・権限付与などの受付証明には使えません。

STATIC_FORM_ACCEPTED_EVENT と StaticFormAcceptedEventDetail は core と adapter から export します。Next のブラウザ用コードでは @200stack/static-forms-next/react から import してください。リスナーの Promise は待たず、通常の DOM リスナーの例外はブラウザが報告します。通知をキャンセルして完了処理を止めることはできません。

GTM と GA4 の例は どちらか一方 を使ってください。リスナーはサイト全体で 1 個とし、必要なら slotId で対象を絞ります。redirect で同じ成果をイベントとサンクスページの両方で数えないでください。移動前の通知は計測先への配送完了を保証しません。

計測は、サイトの Cookie の同意の管理(GTM・GA4 の同意モードなど)に従って送ってください。EU などでは計測に同意が要るのが一般的です。例は入力値を送りません。入力値を計測に足さないでください。

共有の client component を App Router の root layout または Pages Router の _app に一度置きます。次は GTM の例です。

// components/StaticFormAnalytics.tsx
'use client'
import { useEffect } from 'react'
import { STATIC_FORM_ACCEPTED_EVENT, type StaticFormAcceptedEventDetail } from '@200stack/static-forms-next/react'

type AnalyticsWindow = Window & {
  dataLayer?: Array<Record<string, unknown>>
  gtag?: (...args: unknown[]) => void
}

export default function StaticFormAnalytics() {
  useEffect(() => {
    const analytics = window as AnalyticsWindow
    const onAccepted = (event: Event) => {
      const { slotId } = (event as CustomEvent<StaticFormAcceptedEventDetail>).detail
      if (slotId !== 'slot.contact') return
      analytics.dataLayer = analytics.dataLayer || []
      analytics.dataLayer.push({ event: 'static_form_accepted', form_slot: slotId })
    }
    document.addEventListener(STATIC_FORM_ACCEPTED_EVENT, onAccepted)
    return () => document.removeEventListener(STATIC_FORM_ACCEPTED_EVENT, onAccepted)
  }, [])
  return null
}

GTM ではカスタムイベント static_form_accepted をトリガーにし、GA4 イベントタグの名前を generate_lead、パラメーター form_slot を同名のデータレイヤー変数に設定します。GA4 を直接呼ぶ場合は、上の dataLayer の 2 行を次に置き換えてください(リスナーは追加しません)。

if (typeof analytics.gtag === 'function') {
  analytics.gtag('event', 'generate_lead', { form_slot: slotId })
}

CSP では共有 JS の配信元を script-src で許可します。インラインの計測タグを置く場合は hash または CSP と一致する nonce が必要です。GTM・GA 自体の配信元の script-src と送信先の connect-src なども別途許可してください。

開発中の表示

アプリの設定の「開発用トークン」でトークンを発行してください。発行・利用できるのは admin と developer です。アプリを実行するサーバー側の環境に、次を設定します。値は、開発用トークンの画面に出る環境変数の例に置き換えてください。STATIC_FORMS_DEV_ENVIRONMENT_ID には、アプリのデフォルトブランチの環境の ID が自動で入っているので、そのまま使ってください。表示されるのは、そのアプリに接続したフォームです(環境・ブランチごとには分かれません)。ローカルの画面に「環境・branch に対応する設置がありません。」と出たら、開発用トークンの画面で今の環境 ID を確かめ、STATIC_FORMS_DEV_ENVIRONMENT_ID を置き換えてください。

export STATIC_FORMS_DEV_ENABLED=1
export STATIC_FORMS_DEV_API_URL=https://dashboard.example.com
export STATIC_FORMS_DEV_TOKEN=sfdev_REPLACE_WITH_ISSUED_TOKEN
export STATIC_FORMS_DEV_APP_ID=app-id
export STATIC_FORMS_DEV_ENVIRONMENT_ID=environment-id
export STATIC_FORMS_DEV_BRANCH=feature/contact

API URL は HTTPS の origin を指定します。STATIC_FORMS_DEV_BRANCH はフォームの選び分けには使いません。作業中のブランチ名を入れておくと、保存データが別のブランチで取ったものでないかを確かめられます。branch を付けずに取った開発用のデータ(scope の branch が *)を使うときは * を入れてください。git からは推測しません。トークンを NEXT_PUBLIC_*・PUBLIC_* に入れないでください。

next dev では、公開版の入力を開発用プレビューとして表示します。送信はできず、クライアントの送信 controller も起動しません。本番のビルドや実行種別が不明の場合は開発 API を呼びません。ビルド用の入力・pin・STATIC_FORMS_BUILD_ONLY などがあれば、開発サーバーでも本番の入力を使います。入力が無ければエラーになります。

API のデータを保存して使う場合は、プロジェクトのルートで次を実行します。

npx static-forms pull
export STATIC_FORMS_DEV_SNAPSHOT_PATH=.static-forms/development.json

既定の保存先は配信対象外の .static-forms/development.json です。既存ファイルを更新する場合は npx static-forms pull --force を使います。保存データにトークンは含まれません。

保存した snapshot が指定されていれば API より優先します。読み込みに失敗しても API に切り替えず、フォームの位置に診断を表示します。保存データを読む場合も、開発の有効化とアプリ・環境・STATIC_FORMS_DEV_BRANCH の設定は必要です。