Content

Content tools

Everyday tools built into the admin: navigation menus, draft preview links, scheduled backups, site search, image optimization, custom content types, author and contributor roles, and forms.

Admin → Content → Menus (/admin/menus, admins) edits the header, footer and other navigation without code changes; saving applies at once.

  • Each menu has a location (primary = header, footer = footer links, or your own) and a language, one per language; a new one can copy another.
  • Items are pages, posts, products, post or product categories, or custom links (/about, https://…, #anchor, mailto:); javascript: and other unsafe links are refused.
  • Drag to reorder, buttons to nest or un-nest, up to three levels; optional new tab.
  • Renamed slugs follow; unpublished or deleted content is left out.

The base layout uses a primary menu for the header when there is one (items with children become dropdowns, the parent's own link first) and a footer menu for the footer's company column; without menus it keeps its built-in navigation. Site layouts read menus with:

const { items } = useMenu('primary') // null when there is no such menu
// items: [{ id, label, to, external, newTab, children: [...] }]

API: GET /api/public/menus/:key?locale=us (publicly cached, cleared when a menu is saved).

In the post, page or product editor, Preview link creates a link valid for 1 / 7 / 30 days: whoever has it sees this unpublished item with the site's real pages, no account needed.

  • Opening it remembers the preview in the browser and shows a "preview mode" bar with an exit.
  • Only that item is opened up; preview requests skip every cache and carry noindex.
  • Links are signed with a key derived from NUXT_JWT_SECRET: they cannot be forged or pointed at another item, and they expire.
  • Sites with their own editor (ContentEditor) add <ContentPreviewLink v-if="id" :kind="type" :ref-id="id" /> (nuxtcms doctor reports it); sites that override the public detail APIs use publishedOrPreview().

Scheduled backups

Admin → System → Backups (/admin/backups, admins):

  • Daily or weekly at a set hour (site time zone), or Back up now; the newest N are kept.
  • Destinations (any combination):
    • Attachment storage (R2 / OSS / COS / S3 / local) under private/backups/, never public, downloadable from the history;
    • Storage bucket (a backup bucket apart from the site's files): Cloudflare R2, AWS S3, Alibaba OSS, Tencent COS or any S3-compatible storage (MinIO, Backblaze B2... with an endpoint). Bucket, region, access key and directory, with Test connection (writes, lists and deletes a test file); the secret is stored encrypted and the directory keeps the newest N. Use a key that can only read and write this bucket.
    • FTP server: host, port, user, password and directory (created when missing), FTPS (AUTH TLS) optional, with Test connection. The password is stored encrypted.
  • A backup is a .json.gz with what Tools → Export holds: posts, pages, products, categories, translations, public settings and the media list, restorable in Tools → Import. It does not hold accounts, secrets, the media files or plugin data (orders etc.): back up the database for those (Cloudflare D1 has 30-day point-in-time recovery).
  • Scheduled runs come from the cron worker (/api/cron/dispatch, NUXT_CRON_SECRET); without it only manual backups run.
  • On Cloudflare FTP goes through TCP sockets. FTPS servers that require TLS session reuse (vsftpd require_ssl_reuse=YES) only work from Node deployments, or with that option off.

Visitors search published posts, pages and products of their language at /search?q=… (the base header has a search button):

  • several words must all match, "quoted phrases" match as a whole; title hits rank first and results show a highlighted excerpt;
  • substring matching, so Chinese needs no tokenizer; the same on SQLite / D1, PostgreSQL, MySQL;
  • the index follows every save, publish, unpublish and delete; content from before the upgrade is added by the cron (200 per run) or rebuilt at once in Tools → Site search index;
  • API GET /api/search?q=&locale=&kind=post|page|product&limit= (not publicly cached, 60 requests a minute per IP).

Image optimization

Images uploaded in the admin (media library, image picker, pasted or dropped into the editor) are prepared in the browser, so the server does no image work (edge CPU limits):

  • JPEG / PNG / WebP larger than 2560 px or 200 KB are scaled to 2560 px on the longer side and turned into WebP (only when that is smaller; GIF, SVG and small images stay as they are), phone photos turned upright from EXIF;
  • a 480 px WebP preview is made for the media library and the picker;
  • the media library stores the pixel size and an editable alt text.

The upload limit after that stays 5 MB (photos of tens of MB are shrunk first).

Medium quality first, then the original

Photos also get a medium-quality copy on upload (1024 px on the longer side, WebP), kept only when it is clearly smaller. Server-rendered pages show the copy first (usually a tenth to a third of the original) and the original once the page has loaded and the image is in view; images far below still load lazily. Admin pages are not affected.

  • Older uploads: Make medium-quality copies in the media library makes them in the browser.
  • Static images of the site's templates (public/, e.g. a hero image): run npx nuxtcms images (needs sharp; or node scripts/cli.mjs images --dir <site> in the nuxtcms repository), commit public/_mid/ and its manifest, deploy.
  • Off: NUXT_PROGRESSIVE_IMAGES=false. Not active under nuxt dev (hydration warnings).

Custom content types

Admin → Content → Content types creates your own content without code: case studies, team members, FAQs, events... Start from a preset or add fields yourself:

  • field types: text, long text, rich text, number, switch, date, image, images, link, choice, list; any field can be required;
  • each type gets its own item under "Content" in the sidebar; entries have draft / published / scheduled, revisions and languages;
  • public types get default pages, /<locale>/c/<key> (list) and /<locale>/c/<key>/<slug>, and appear in the sitemap and site search; private types are for site code only;
  • in site code: useCollection('cases'), useCollectionItem('cases', slug), or GET /api/public/collections/:key and /api/public/collections/:key/:slug; <CmsFieldValue> shows a field value;
  • deleting a type asks for its key and removes its entries.

Authors and contributors

Admin → Users has two roles besides administrator and editor:

RoleCan
Administratoreverything, including settings, users, plugins and security
Editorall posts, pages, products, content types and media
Authorwrite and publish their own posts, upload images
Contributorwrite their own posts as drafts for an editor to publish; use existing images only

Authors and contributors see only those menu items; the server lets them call the posts, media, preview and own-account APIs only (no plugin APIs) and open or change their own posts only.

Several users and forgotten passwords (the mail settings must be configured):

  • When creating a user, tick "Send an invitation": they set their own password from the e-mailed link (valid for 3 days).
  • The security menu of each user can "Send password reset e-mail".
  • The sign-in page has "Forgot password?": an admin address gets a reset link (1 hour, single use). The answer is the same for unknown addresses; requests are rate limited per IP and address and use the sign-in human check. After a reset every device signs in again.
  • Nothing is stored: the token carries a fingerprint of the current password, so a changed password retires old links.

Forms

Admin → Content → Forms builds quote requests, sign-ups, surveys...

  • fields: text, e-mail, phone, long text, number, drop-down, single choice, multiple choice, date, consent checkbox; labels per language;
  • [form form-key] on its own line in page or post content shows the form (the code button in the list copies it); in site code use <CmsForm form="form-key" />;
  • answers are checked again on the server, rate limited per visitor IP (5 per form per 10 minutes) and can require the captcha (the "contact form" scene in Security);
  • submissions are kept in the admin: mark read, archive, delete, CSV export (opens in Excel, formula cells defused); an optional notification e-mail per submission;
  • each submission emits form.submitted, so robot notification and webhook plugins can forward it;
  • closed forms take no submissions.

Testing

pnpm smoke:content --base http://localhost:3192 covers menus (unpublished content hidden, cache cleared, unsafe links), preview links (signature, tampering, one item only, no caching), backups (storage and FTP, rotation, download rights, no secrets; the script runs its own fake FTP server), site search (published only, language and kind filters, ranking, removal on unpublish), media (previews, alt text, previews deleted with their file), content types (field checks, drafts and publishing, search, public or not), roles (what authors / contributors may do) and forms (validation, submissions, CSV, closing).