Deft
Ask AI

Search

Search skills, authors and topics

higgsfield-ai / skills

higgsfield-websites

Build, edit, and deploy full-stack websites, apps and games via the Higgsfield CLI (`higgsfield website …`). Each is a React 19 + TanStack Start SSR app in one Cloudflare Worker (D1/R2/KV/DO/Containers). THREE product types, picked via `--type` on create: `website` (standalone, no Higgsfield integration — references/website-flow.md), `app` (Sign in with Higgsfield + fnf SDK, Quanta — references/app-flow.md), `game` (realtime multiplayer rooms — references/game-flow.md). Routes to the right flow;

Install

npx skills add https://github.com/higgsfield-ai/skills --skill higgsfield-websites

Skill: Auth Boundary

Use this when a website needs signed-in user state, login, logout, account-gated pages, profile loading, or protected file downloads. This template does not implement its own identity provider. The platform owns auth and exposes a small contract to the website.

Contract

  • Browser code must call app-local endpoints only.
  • Server code may call https://fnf.internal/*.
  • Do not send auth headers to fnf.internal; identity is attached by the platform/runtime automatically.
  • Do not call https://fnf.internal/* from the browser. It is not reachable there and would be a security bug even if it were.

Endpoints:

PurposeEndpointCaller
Current user profileGET https://fnf.internal/userserver only
Browser-safe user proxyGET /api/userfrontend/browser
Start loginGET /__auth/login?return=<path>browser navigation
Start logoutGET /__auth/logout?return=<path>browser navigation

GET https://fnf.internal/user returns the current user's profile JSON and returns 401 when the visitor is not signed in.

GET /api/user must call https://fnf.internal/user server-side and return the same status code and JSON body unchanged. This endpoint is the only user-profile endpoint browser components should fetch.

Choose The Correct Auth Mode

There are two different auth concepts. Pick intentionally:

Website needAuth modeRequired behavior
Higgsfield SDK/model generation, media upload, profile, workspace, credits, feed/historyHiggsfield platform authUse /api/user, /__auth/login, /__auth/logout, and server-side https://fnf.internal guards
The generated website's own product accounts, for example todos, notes, CRM records, dashboards, membershipsIn-app authBuild app-local auth/session/storage. Do not call fnf.internal/user unless the website also uses Higgsfield SDK features
Both generation and product accountsBoth, kept separateGate SDK routes with Higgsfield auth and product data with in-app auth. Label UI clearly and never mix identities

If the prompt mentions Higgsfield, SDK, model generation, Nano Banana, Seedance, image/video generation, media upload, credits, workspaces, or generation history, Higgsfield auth is implicit even if the user did not say "add sign in."

If the prompt only asks for "sign in" for a normal generated product and does not use Higgsfield generation/profile/credits, default to in-app sign in. Do not use /__auth/login as the product's account system unless the prompt specifically asks for Higgsfield account sign-in.

Required /api/user Route

Create a TanStack Start server route, not a separate Hono/Express API:

ts
// app/src/routes/api/user.tsimport { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/user')({  server: {    handlers: {      GET: async () => {        const upstream = await fetch('https://fnf.internal/user')        const body = await upstream.text()
        return new Response(body, {          status: upstream.status,          headers: {            'content-type': upstream.headers.get('content-type') ?? 'application/json',            'cache-control': 'no-store',          },        })      },    },  },})

Notes:

  • Preserve upstream status. A 401 from fnf.internal/user must remain 401 at /api/user.
  • Preserve upstream JSON body unchanged. Do not reshape fields in the proxy.
  • Add cache-control: no-store; user identity is per request.
  • Do not add Authorization, cookies, service secrets, hf-user-id, or marketplace headers. The platform injects identity on server egress.
  • Do not expose raw fnf internal URLs to the UI.

Frontend User Loader Pattern

Use /api/user from loaders, queries, or components:

ts
export async function fetchCurrentUser() {  const response = await fetch('/api/user', { credentials: 'include' })  if (response.status === 401)    return null  if (!response.ok)    throw new Error('Failed to load user')  return response.json()}

For route loaders, return null for signed-out users rather than crashing the page. For account-required pages, redirect or render a sign-in action.

Login

Login is browser navigation, not an SDK call:

ts
function login(returnPath = window.location.pathname + window.location.search) {  window.location.href = `/__auth/login?return=${encodeURIComponent(returnPath)}`}

Rules:

  • Use the current path as return by default.
  • Encode the return path.
  • Keep the return path app-local. Do not pass full external URLs.
  • After login completes, the platform redirects back to return; then /api/user should succeed.

Logout

Logout is also browser navigation:

ts
function logout(returnPath = '/') {  window.location.href = `/__auth/logout?return=${encodeURIComponent(returnPath)}`}

Rules:

  • Use / or the current public route as the default return.
  • Clear client-side query/cache state after navigation if the website stores user, workspace, feed, or credits data in TanStack Query.
  • Do not try to delete platform cookies manually from website code.
  • Do not call fnf.internal for logout.

Authenticated File Downloads

Apps may run inside a cross-origin authenticated iframe. Native download navigation may not preserve that embedded authentication context and can return 401 Unauthorized. For a protected app-local /api/... file, NEVER use:

  • <a href="/api/..." download>
  • window.location
  • window.open

Fetch the file with credentials and download the successful response through a temporary Blob URL instead. This is a browser-only helper: call it from an event handler, never during SSR or module initialization.

ts
export async function downloadAuthenticatedFile(  url: string,  filename: string,): Promise<void> {  const downloadUrl = new URL(url, window.location.href)  if (    downloadUrl.origin !== window.location.origin    || !downloadUrl.pathname.startsWith('/api/')  ) {    throw new Error('Authenticated downloads require an app-local /api/... URL')  }
  const response = await fetch(downloadUrl, {    credentials: 'include',  })
  if (!response.ok) {    throw new Error(`Download failed (${response.status})`)  }
  const blobUrl = URL.createObjectURL(await response.blob())  const anchor = document.createElement('a')
  try {    anchor.href = blobUrl    anchor.download = filename    anchor.style.display = 'none'    document.body.appendChild(anchor)    anchor.click()  }  finally {    anchor.remove()    window.setTimeout(() => URL.revokeObjectURL(blobUrl), 0)  }}

Call the helper from a real Quanta button. The handler owns loading state and must catch and surface download errors through designed inline feedback or a toast.

tsx
<Button  type="button"  disabled={isDownloading}  onClick={handleDownload}>  {isDownloading ? 'Downloading…' : 'Download'}</Button>

Requirements:

  • The protected server route must re-check the current user and authorize that specific file. Return 401/403 for unauthenticated/forbidden requests; never redirect a download fetch to an HTML login page. Serve the file with a truthful Content-Type and, when the route owns the filename, matching Content-Disposition.
  • Pass credentials: 'include'. The helper enforces a same-origin /api/... URL; never weaken that check or pass it an untrusted arbitrary URL.
  • Check response.ok before reading the body.
  • Show loading and error states.
  • Remove every temporary element and revoke every object URL after the click has had a browser task to start the download.
  • Do not nest a <button> inside an <a>.
  • Raw download links are allowed only for public static files or self-contained signed URLs that require no session.
  • For large files, prefer a backend-generated short-lived signed URL with Content-Disposition: attachment to avoid buffering the entire file in browser memory.
  • Test downloads inside the Higgsfield iframe, not only with the app opened directly. Test both newly generated files and previously saved files.
  • If a sandboxed host iframe blocks programmatic downloads, the host must grant its download capability. Report that platform constraint; do not work around it by navigating to the protected URL.

For generated-media exports, also apply references/fnf-sdk.md → “Output and download format contract” so the downloaded bytes, MIME type, extension, and filename agree. A direct getRawUrl() link is safe only when it is public or a self-contained signed URL that needs no session; otherwise use the helper.

Auth UI Rules

  • Signed-out state should show a clear sign-in action using the website's normal Quanta button style.
  • Do not build email/password forms unless the task explicitly asks for a custom identity UI and the platform supports it. The default is /__auth/login.
  • Do not show fake profile data while loading. Use skeletons or neutral loading states.
  • Never render emails, names, workspace names, tokens, or raw profile payloads into observability events.

FNF SDK Interaction

For generated websites using the fnf SDK, create SDK adapters server-side with baseUrl: 'https://fnf.internal'. The auth boundary above is for website UI identity. SDK calls should still use server functions or server-only modules and must not be made directly from browser components.

Required Auth For SDK Features

Any website that uses SDK-backed generation must be authenticated. This includes:

  • generation submit, poll, cancel, feed/history, and cost preview
  • media upload, media resolve, and media reads
  • profile, workspace switch, wallet, and credits
  • any UI that exposes model settings or writes paid/user-owned outputs

When the user asks for a Higgsfield generation website, model form, image/video generator, Nano Banana/Seedance/etc. website, or anything that uses the SDK, auth is implicit even if the user does not say "add sign in." Add sign-in/logout, /api/user, server-side auth guards, profile/credits/workspace UI, and a feed/history surface automatically.

The only exception is an offline/mock demo the user EXPLICITLY asked for — memory adapters only, never calling fnf.internal, apps-marketplace, media upload, or profile endpoints. Never choose a mock as the default: a real SDK app is end-to-end, with a real backend and D1 persistence (see references/app-flow.md rule 3a).

Required flow:

  1. Implement /api/user.
  2. Browser loads /api/user before rendering SDK-backed controls.
  3. If /api/user returns 401, show a signed-out state and a Quanta sign-in button that navigates to /__auth/login?return=<current path>.
  4. Disable or hide submit/upload/cost/profile controls while signed out.
  5. Every server function or server route that performs an SDK operation must call https://fnf.internal/user first and stop on 401.

The UI gate is for experience; the server-side auth check is the actual safety boundary. Do not rely on "button hidden when signed out" as protection.

If a page needs both user state and generation data:

  1. Browser calls /api/user for display/auth gate.
  2. Server function submits/reads jobs through the selected SDK adapter.
  3. Browser receives only safe website data: user-safe fields, generation ids, statuses, display credits, and sanitized errors.

Server Auth Guard Pattern

Use a small server-only helper for SDK server functions:

ts
// app/src/lib/auth.server.tsexport async function requireCurrentUser() {  const response = await fetch('https://fnf.internal/user')  const body = await response.json().catch(() => null)
  if (response.status === 401) {    return {      ok: false as const,      status: 401,      body,    }  }
  if (!response.ok) {    return {      ok: false as const,      status: response.status,      body,    }  }
  return {    ok: true as const,    user: body,  }}

Then guard SDK operations:

ts
const auth = await requireCurrentUser()if (!auth.ok) {  return new Response(JSON.stringify(auth.body), {    status: auth.status,    headers: { 'content-type': 'application/json' },  })}
// Safe to create SDK adapter/client and submit/read user-owned data here.

For createServerFn, return a typed { ok: false, code: 'unauthorized' } result or throw/redirect according to the website's route behavior, but still check auth before the SDK call.

Signed-Out UI Pattern

Use this Quanta pattern for app-shaped generation/tool UIs. Marketing waitlists and landing pages should use custom sign-in chrome per references/design-taste-frontend.md instead of Quanta buttons.

tsx
import { Button } from '@higgsfield/quanta/button'
function SignInRequired() {  return (    <div className="grid min-h-72 place-items-center rounded-lg border border-q-border-subtle bg-q-background-secondary p-6 text-center">      <div className="grid max-w-sm gap-3">        <h2 className="text-q-title-md-semi-bold">Sign in to generate</h2>        <p className="text-q-body-sm-regular text-q-text-secondary">          Generation, uploads, credits, and history are connected to your account.        </p>        <div>          <Button            onClick={() => {              const returnPath = window.location.pathname + window.location.search              window.location.href = `/__auth/login?return=${encodeURIComponent(returnPath)}`            }}          >            Sign in          </Button>        </div>      </div>    </div>  )}

Do not render active prompt fields, upload controls, generate buttons, cost buttons, workspace switchers, or job feeds as usable controls while signed out.

Anti-Patterns

ts
await fetch('https://fnf.internal/user') // bad in browser codeawait fetch('/api/user')                 // good in browser code
ts
fetch('https://fnf.internal/user', {  headers: { Authorization: `Bearer ${token}` },}) // bad; platform attaches identity automatically
ts
window.location.href = '/login' // bad; not the platform auth routewindow.location.href = '/__auth/login?return=%2Fdashboard' // good
ts
// bad: anonymous generation surfaceawait jobs.submit(input)
// good: server-side auth gate firstconst auth = await requireCurrentUser()if (!auth.ok) {  return new Response(JSON.stringify(auth.body), {    status: auth.status,    headers: { 'content-type': 'application/json' },  })}await jobs.submit(input)