- Go backend lives in
backend/. - Redis stores one-time secret metadata and counters.
FILE_STORAGE_DIRstores encrypted uploaded files on disk as*.encblobs.- File shares use Redis for one-time metadata plus disk storage for the encrypted blob itself.
- React frontend lives in
frontend/and uses Next.js with static export (output: 'export'). - Server-rendered HTML flows in
templates/are deprecated.
- Entry point:
backend/main.go - HTTP handlers:
backend/handlers.go - Redis access:
backend/storage.go - File upload/download API endpoints live in
backend/handlers.goas/api/saveFileand/api/getFile. - Backend file size limit is
10 MBviamaxFileSizeinbackend/handlers.go. - Uploaded encrypted files are written to
FILE_STORAGE_DIR/<id>.encand the Redis record stores the path plus hashed key. backend/storage.goruns a file janitor every 2 hours and deletes expired*.encfiles based on file mtime.- Stored file counters use Redis keys
stats:stored:file:totalandstats:stored:file:day:YYYYMMDD. - The backend listens on
127.0.0.1:8080. - Required env:
FILE_STORAGE_DIR=/absolute/path/to/encrypted-filesREDISHOST=127.0.0.1:6379REDISPASS=
- Optional env:
LISTEN_ADDR=127.0.0.1:8080
Run locally:
go run ./backendBuild/test:
go build ./backend
GOCACHE=/tmp/go-cache go test ./backend/...
make buildmake buildbuilds the frontend production bundle intofrontend/buildand the backend binary intobin/1time-api.
- First-party CLI lives in
cli/and publishes as@1time/cli. - Runtime: Node.js 20+.
- Entry point:
cli/index.mjs - Command implementation:
cli/lib.mjs - Shared encryption protocol:
cli/protocol.mjs - The CLI syncs shared protocol code via
cli/scripts/sync-protocol.mjsbeforenpm test,npm pack, andnpm publish. - Supported commands:
1time send,1time read,1time send-file,1time read-file,--host,-h/--help 1time sendinput precedence is: pipedstdin,1TIME_SECRET, then positional secret argument.- Prefer
stdinforsend; positional secrets leak through shell history and process listings. readandread-filecurrently accept the full secret link as a positional argument only, which also exposes decryption material in shell history and process listings.send-fileandread-filesupport optional passphrases via--passphraseor1TIME_PASSPHRASE.- File links use the
/f/#<randomKey><id>format. read-file --out <path>refuses to start if the target path already exists.read-filewithout--outwrites into the current directory using the original filename and auto-suffixes collisions likereport (1).txt.- Default host is
1time.io; bare hosts normalize tohttps://...; plainhttp://is only allowed for loopback addresses such as127.0.0.1.
Run locally:
node cli/index.mjs --help
printf 'hello' | node cli/index.mjs send
node cli/index.mjs read 'https://1time.io/v/#...'Build/test:
cd cli
npm test
npm pack --dry-run- Toolchain: Next.js 15 with static export
- Runtime: React 19
- Config:
frontend/next.config.js(output: 'export', distDir: 'build', trailingSlash: true) - Root layout:
frontend/app/layout.jsx - Pages:
frontend/app/*/page.jsx(file-based routing) - Client components:
frontend/components/*.jsx(marked with 'use client') - Utils:
frontend/utils/util.js(encryption),frontend/utils/wordlist.js - Styles:
frontend/app/globals.css(base),frontend/styles/*.css(per-component)
Run locally:
cd frontend
npm install
npm run devUseful commands:
cd frontend
npm test
npm run build- The dev server runs on
127.0.0.1:3001. - In normal local development, leave
NEXT_PUBLIC_API_URLunset so the frontend uses relative/api/requests and Next.js proxies them tohttp://127.0.0.1:8080. - To use a different backend target in development, set
API_PROXY_TARGETbeforenpm run dev. - Do not edit
frontend/builddirectly; it is generated output. - SEO metadata is defined via
export const metadatain each page.jsx file, NOT via useEffect. This is critical for Google indexing. - Each page.jsx is a server component that exports metadata and renders a client component from
components/. - The
'use client'directive is required on all interactive components (forms, buttons, state). - The active create/share flow renders the generated secret link inline on the current page.
- The
/v/route reads the secret key from the URL hash (#key), which is client-side only. - File sharing UI lives on
/secure-file-sharing/and rendersfrontend/components/SecureFileShare.jsx. - File download UI lives on
/f/and rendersfrontend/components/ViewSecretFile.jsx. SecureFileShareencrypts the file in the browser, uploads withXMLHttpRequest, and shows upload progress.ViewSecretFilereads the link key from the URL hash first; the component also contains a fallback parser for/f/<key>style paths, but generated file links are hash-based.- Frontend file size limit is
Constants.maxFileSizeBytes = 10 * 1024 * 1024infrontend/utils/util.js; keep it aligned with the backend limit. - File metadata (
name,type,size) is packed into the encrypted payload before upload; the web app server does not store that metadata separately. - Pages with
robots: 'noindex, nofollow'in metadata:/v/,/f/. - Tests live in
frontend/src/App.test.jsxand use vitest + React Testing Library. - The vitest config (
frontend/vitest.config.js) uses esbuild withjsx: 'automatic'to handle JSX in components.
- Treat every
<link rel="stylesheet">infrontend/build/**/index.htmlas render-blocking unless proven otherwise. - Keep
frontend/app/layout.jsxlimited to truly shared base styles only: app chrome, typography tokens, buttons, form primitives, and other classes needed on first paint across most routes. - Do not import route-specific CSS into
frontend/app/layout.jsx. - If a stylesheet is specific to one route family and required for first paint, emit it from that route's server
page.jsxorlayout.jsx, preferably viafrontend/components/InlineCss.jsx, instead of promoting it to the root layout. - If UI only appears after user interaction (success states, generated-link panels, drawers, modals, secondary tools), lazy-load the component so its JS and CSS stay out of the initial render path.
- Do not statically import post-interaction components from large entry components when the initial screen can render without them.
- Before merging frontend UI changes, run
cd frontend && npm run buildand inspect the generated HTML for the affected route to confirm it is not pulling unrelated CSS chunks. - A route should not ship CSS for unrelated pages such as blog, stats, view, about, or post-submit states during initial render.
- Lighthouse performance is a hard constraint, not a nice-to-have. Treat regressions as bugs unless there is a clear product reason.
- Static content routes such as
/blog/**and other read-mostly pages should stay as close to zero-JS as practical. Do not add client components to content pages unless the interaction is essential. - Treat every
<script src>infrontend/build/**/index.htmlas suspect on content routes. Verify whether each chunk is required for user-visible behavior on that route. - Do not pull generator, share-flow, stats, view-secret, or other app-tooling bundles into blog or marketing pages.
- Avoid putting client components in
frontend/app/layout.jsxwhen a server component, plain markup, or a tiny standalone script would work. Shared client components in the root layout force hydration across the whole site. - For static pages, prefer plain links and server-rendered navigation patterns when they materially reduce app-router hydration cost.
- If a page is primarily article or marketing content, optimize for first-load HTML and CSS first, and only then add JavaScript that is strictly necessary.
- After frontend performance changes, build with
cd frontend && npm run buildand inspect the affected HTML for both CSS and JS:- confirm there are no unrelated route chunks
- confirm route-specific assets stay route-specific
- confirm content pages are not loading interactive app bundles
- If you self-host fonts or other root-level static assets outside
/_next/static/, make sure deployment config adds explicit cache headers for them.
- Public domain is
https://1time.io.
- Frontend production build output:
frontend/build(static HTML files per route) - Backend production binary from
make build:bin/1time-api - Example nginx config:
configs/nginx/1time.conf - nginx serves frontend statics and proxies
/apito the Go app on127.0.0.1:8080. - nginx upload ceiling is
11min bothconfigs/nginx/1time.confanddocker/nginx/default.conf.templateto stay above the backend's10 MBmultipart limit. - Host nginx has an exact
/f/location with the same sensitive-header treatment as/v/. - The nginx
try_filesdirective includes$uri/index.htmlfor Next.js trailing-slash static export.
- The React frontend uses the JSON API routes under
/api. - Each route generates its own
index.htmlwith full pre-rendered content and unique meta tags for SEO. - The deprecated server-rendered
/view/...flow is separate from the SPA/v/flow. - File links are one-time and currently use the SPA
/f/flow. - File downloads are consumed on the first authorized fetch attempt: the Redis file record is deleted before transfer completion is known, and the disk blob is removed after the stream attempt.