Skip to content

Site Configuration

The site configuration is the core configuration file of the Firefly theme, controlling basic site information, theme colors, page toggles and other global settings.

Config File

src/config/siteConfig.ts

Basic Information

PropertyTypeDefaultDescription
titlestring"Firefly"Site title
subtitlestring"Demo site"Site subtitle
site_urlstring-Site URL
descriptionstring-Site description for <meta name="description">
keywordsstring[]-Site keywords for <meta name="keywords">
langstring"zh_CN"Site language: zh_CN, zh_TW, en, ja, ru, ko. Overridable via PUBLIC_SITE_LANG (see tip below)
ts
export const siteConfig: SiteConfig = {
  title: "Firefly",
  subtitle: "Demo site",
  site_url: "https://firefly.cuteleaf.cn",
  description: "A beautiful Astro blog theme...",
  keywords: ["Firefly", "Astro", "Blog"],
  lang: "zh_CN",
};

Override site language via environment variable

lang can be overridden by the PUBLIC_SITE_LANG environment variable, which takes priority over the config file. For example, set PUBLIC_SITE_LANG=en on your deployment platform (Vercel / Cloudflare, etc.) to build the whole site (navbar, page text) in English without touching the config. Accepts values like en / zh_cn / zh_tw / ja / ja_jp / ru / ko (case-insensitive); falls back to the config default if unrecognized.

Theme Color

PropertyTypeDefaultDescription
themeColor.huenumber165Theme color hue (0-360). Red: 0, Cyan: 200, Teal: 250, Pink: 345
themeColor.defaultModestring"system"Default mode: "light", "dark", "system"

Page Width

PropertyTypeDefaultDescription
pageWidthnumber100Maximum page width in rem. Larger values make the content area wider
ts
// Page width (unit: rem)
// Increase the value to make the content area wider
pageWidth: 100,

Card Style

PropertyTypeDefaultDescription
card.borderbooleantrueEnable card border and shadow for a 3D effect
card.followThemebooleanfalseWhether card background follows theme hue in light mode
ts
card: {
  border: true,
  followTheme: false,
},
PropertyTypeDefaultDescription
navbar.logoobject-Navbar logo, see below
navbar.titlestring"Firefly"Navbar title
navbar.widthFullbooleanfalseWhether navbar takes full width
navbar.menuAlignstring"center"Desktop menu alignment: "left" or "center"
navbar.followThemebooleanfalseWhether navbar icon and title follow theme color
navbar.navbarMode"static" | "fixed" | "dynamic""fixed"Navbar mode: static scrolls away; fixed stays pinned at the top; dynamic is pinned but hides on scroll down and reveals on a slight scroll up

Logo supports four types:

  1. Astro icon library: { type: "icon", value: "material-symbols:home-pin-outline" }
  2. public directory image (no optimization): { type: "image", value: "/assets/images/logo.webp", alt: "Logo" }
  3. src directory image (auto-optimized, recommended): { type: "image", value: "assets/images/logo.webp", alt: "Logo" }
  4. Remote image: { type: "url", value: "https://example.com/logo.png", alt: "Logo" }
PropertyTypeDefaultDescription
logo.typestring-"icon", "image" or "url"
logo.valuestring-Icon name, image path or image URL
logo.valueDarkstring-A different image for dark mode. Only applies to image and url types
logo.altstring-Alt text for the image

With valueDark set, light and dark mode each show their own image and switching themes takes effect instantly. If omitted, both modes share value. Icon library logos (type: "icon") follow the text color, so no separate dark variant is needed.

ts
navbar: {
  logo: {
    type: "image",
    value: "assets/images/firefly.png",
    // Use a different image in dark mode (optional)
    valueDark: "assets/images/firefly-dark.png",
    alt: "🍀",
  },
  title: "Firefly",
  widthFull: false,
  menuAlign: "center",
  followTheme: false,
  navbarMode: "fixed",
},

TIP

The share poster header shows the navbar logo and the site title. Since the poster background is always white, the poster always uses the light-mode logo.

Favicon

ts
favicon: [
  {
    src: "/favicon/favicon.ico",
    // theme: "light",  // Optional: 'light' | 'dark'
    // sizes: "32x32",  // Optional: icon size
  },
],

Date & Timezone

PropertyTypeDefaultDescription
siteStartDatestring-Site start date (YYYY-MM-DD), used for uptime counter
timezonestring"Asia/Shanghai"IANA timezone string for date formatting

Admonitions

PropertyTypeDefaultDescription
rehypeCallouts.themestring"github"Theme: "github", "obsidian", "vitepress", "docusaurus"
rehypeCallouts.enablePythonMarkdownAdmonitionsbooleanfalseEnable Python Markdown style admonition syntax (using !!! instead of > [!NOTE])

TIP

Restart the dev server after changing this setting.

Post Settings

PropertyTypeDefaultDescription
post.showLastModifiedbooleantrueShow "last modified" card at the bottom of posts
post.outdatedThresholdnumber30Days threshold for showing the "last modified" card
post.sharebooleantrueShow share button on post pages
post.postNavigationbooleantrueShow previous/next post navigation
post.relatedPostsbooleantrueShow related posts recommendation
post.randomPostsbooleantrueShow random posts recommendation
post.generateOgImagesbooleanfalseGenerate OpenGraph images (increases build time)

Post List Layout

PropertyTypeDefaultDescription
postListLayout.defaultModestring"list"Default layout: "list" or "grid"
postListLayout.mobileDefaultModestring-Mobile default layout: "list" or "grid". If not set, it follows defaultMode
postListLayout.coverPositionstring"right"Cover image side in list mode: "right" or "left". Grid mode always places the cover on top and is unaffected
postListLayout.descriptionLinesnumber2Number of lines for post excerpts. Set to 0 to disable truncation
postListLayout.showStatsIconsbooleantrueShow icons in the post card footer stats (published date, word count, reading time)
postListLayout.tagsPositionstring"meta"Tag display position: "meta" shows in the metadata row below the title, "bottom" shows at the card bottom (replaces stats display, only one can be chosen). In "bottom" mode, tags beyond meta.tagCount are collapsed into a +N pill; hover it to see the hidden tags
postListLayout.tagsBottomStylestring"chip"Bottom tag style, only effective when tagsPosition is "bottom": "chip" filled buttons whose shape follows tagStyle; "text" plain text with no background
postListLayout.grid.masonrybooleanfalseEnable masonry layout
postListLayout.grid.columnWidthnumber320Minimum card width in grid mode (px). The browser automatically calculates column count based on container width
postListLayout.grid.coverFullWidthbooleanfalseWhether the grid-mode cover bleeds to the card edges. true makes the cover reach the top, left and right edges with only the top two corners rounded; false insets the cover by the card padding, leaving a gap on the top, left and right with all four corners rounded (the gap follows the screen width, matching the card padding)

PostMeta Display Control

Controls the display of each element in the metadata row below the post card title.

PropertyTypeDefaultDescription
postListLayout.meta.showPublishedbooleantrueShow published date
postListLayout.meta.showCategorybooleantrueShow category
postListLayout.meta.showTagsbooleantrueShow tags
postListLayout.meta.tagCountnumber1Number of tags to display. Set to 0 for no limit. When tagsPosition is "bottom", extra tags are collapsed into a +N pill
postListLayout.meta.showWordsbooleantrueShow word count
postListLayout.meta.showReadingTimebooleantrueShow reading time

PostStats Display Control

Controls the display of each element in the post card footer stats bar. When tagsPosition is set to "bottom", stats will not be displayed.

PropertyTypeDefaultDescription
postListLayout.stats.showPublishedbooleantrueShow published date
postListLayout.stats.showWordsbooleantrueShow word count
postListLayout.stats.showReadingTimebooleantrueShow reading time

Category and Tag Styles

Both the category navigation buttons and the tag chips can switch between a "pill" and a "rectangle" shape. The two options are independent of each other.

PropertyTypeDefaultDescription
categoryStylestring"rectangle"Category navigation button style: "pill" or "rectangle"
tagStylestring"pill"Tag style: "pill" (theme-colored background), "pill-gray" (neutral grey background), or "rectangle" (theme-colored background)
ts
categoryStyle: "rectangle",
tagStyle: "pill",

categoryStyle

Shape only — no color change. "rectangle" keeps every color from the pill variant (including hover, active-category highlight and the post-count badge) and merely reduces the corner radius.

It applies to the category navigation bar only (top of the homepage and archive page, toggled by categoryBar). The filter buttons on the friends, gallery and bookmark pages are unaffected and always stay pills.

tagStyle

ValueAppearance
"pill"Fully rounded pill on a theme-colored background (light tint of --btn-content), deepening to the theme accent on hover
"pill-gray"Fully rounded pill on a neutral grey background, turning theme-colored on hover
"rectangle"Slightly rounded rectangle using the theme color (--btn-regular-bg) directly, with hover / active shade changes

It applies to all three tag locations:

  • Post card tags (in the metadata row, and the bottom tags including the +N collapse pill when tagsPosition is "bottom")
  • Tags on the /tags page and their post-count badges
  • The sidebar tags widget

TIP

When postListLayout.tagsBottomStyle is "text" the bottom tags have no background, so tagStyle has no effect there. The other two locations still follow tagStyle.

Pagination

PropertyTypeDefaultDescription
pagination.postsPerPagenumber10Posts per page

Page Toggles

PropertyTypeDefaultDescription
pages.friendsbooleantrueFriends page toggle
pages.sponsorbooleantrueSponsor page toggle
pages.guestbookbooleantrueGuestbook page toggle (requires comment system)
pages.bangumibooleantrueBangumi page toggle
pages.vndbbooleantrueVNDB page toggle
pages.gallerybooleantrueGallery page toggle
pages.bilibilibooleantrueBilibili page toggle
pages.dynamicbooleantrueMoments page toggle, including its navigation link and sidebar widget
pages.booknavbooleantrueBooknav page toggle
pages.malbooleantrueMyAnimeList page toggle

Environment variable override

Every page toggle can be overridden by an environment variable, which takes priority over the config file. For example, set PUBLIC_PAGES_BILIBILI=true on your deployment platform (Vercel / Cloudflare, etc.) to enable the Bilibili page without touching the config file, or PUBLIC_PAGES_FRIENDS=false to disable the friends page. Values like true / 1 / on enable, false / 0 / off disable.

| categoryBar | boolean | true | Category navigation bar on homepage and archive page |

Display Settings Panel

The display settings panel is opened via the gear icon in the navbar, allowing visitors to customize theme color, wallpaper mode, card style, and more.

See Display Settings Panel for details.

Bangumi

PropertyTypeDefaultDescription
bangumi.userIdstring-Bangumi user ID
bangumi.mode"static" | "dynamic""dynamic"Data mode. static fetches data at build time and renders statically; dynamic fetches data in the browser via API, always showing the latest data
bangumi.apiUrlstring"https://api.bangumi.one"Bangumi API URL
bangumi.subjectBaseUrlstring"https://bangumi.one/subject/"Subject detail page URL
bangumi.categoryOrderstring[]["anime", "book", "music", "game"]Category display order. Available values: "anime" "book" "music" "game" "real"
bangumi.nsfw"off" | "blur" | "hide""off"NSFW handling: off show all, blur blur covers, hide remove entries

TIP

In static mode, dev only fetches one page of data; build fetches all data. In dynamic mode, data is fetched in real-time in the browser and is always up-to-date.

VNDB

PropertyTypeDefaultDescription
vndb.userIdstring""VNDB user ID, e.g. u2
vndb.mode"static" | "dynamic""static"Data mode. static fetches data at build time and renders statically; dynamic fetches data in the browser via API, always showing the latest data
vndb.downloadCoversbooleanfalseDownload and compress covers into public/vndb-covers at build time so images are served from your own server. static mode only
vndb.apiUrlstring"https://api.vndb.org/kana"VNDB API URL
vndb.vnBaseUrlstring"https://vndb.org/"Entry detail page URL, must end with /
vndb.apiTokenstring""Access token for private lists, static mode only. Never commit a real token to a public repository
vndb.nsfw"off" | "blur" | "hide""blur"NSFW handling: off show all, blur blur covers, hide remove entries

See VNDB for details.

Analytics

PropertyTypeDefaultDescription
analytics.googleAnalyticsIdstring""Google Analytics ID
analytics.microsoftClarityIdstring""Microsoft Clarity ID
analytics.umamiAnalytics.websiteIdstring""Umami website ID
analytics.umamiAnalytics.scriptUrlstring"https://cloud.umami.is/script.js"Umami tracking script URL (supports self-hosted Umami)
analytics.umamiAnalytics.replaysScriptUrlstring"https://cloud.umami.is/recorder.js"Umami session replay script URL (supports self-hosted Umami)
analytics.umamiAnalytics.trackOutboundLinksbooleantrueAutomatically add Umami outbound click events to external links
analytics.umamiAnalytics.collectWebVitalsbooleanfalseEnable data-performance="true" to collect Core Web Vitals
analytics.umamiAnalytics.replays.enabledbooleanfalseEnable Umami session replay
analytics.umamiAnalytics.replays.sampleRatenumber0.15Replay sampling rate from 0 to 1; for example, 0.15 records 15% of sessions
analytics.umamiAnalytics.replays.maskLevel"moderate" | "strict""moderate"Privacy masking level; moderate masks inputs, strict also masks all page text
analytics.umamiAnalytics.replays.maxDurationnumber300000Maximum recording length in milliseconds, default 5 minutes
analytics.umamiAnalytics.replays.blockSelectorstring""CSS selector for elements to fully exclude from recording; omitted when empty
analytics.la51Analytics.Idstring""51la analytics ID
analytics.la51Analytics.sdkUrlstring""Custom SDK URL (leave empty to use default)
analytics.la51Analytics.ckstring""Data separation identifier for multiple statistics IDs
analytics.la51Analytics.autoTrackbooleanfalseEnable event analysis
analytics.la51Analytics.hashModebooleanfalseEnable hash route mode
analytics.la51Analytics.screenRecordbooleantrueEnable session recording
ts
analytics: {
  googleAnalyticsId: "",
  microsoftClarityId: "",
  umamiAnalytics: {
    websiteId: "",
    scriptUrl: "https://cloud.umami.is/script.js",
    replaysScriptUrl: "https://cloud.umami.is/recorder.js",
    trackOutboundLinks: true,
    collectWebVitals: false,
    replays: {
      enabled: false,
      sampleRate: 0.15,
      maskLevel: "moderate",
      maxDuration: 300000,
      blockSelector: "",
    },
  },
  la51Analytics: {
    Id: "",
    sdkUrl: "",
    ck: "",
    autoTrack: false,
    hashMode: false,
    screenRecord: true,
  },
},

If you use a self-hosted Umami instance, set analytics.umamiAnalytics.scriptUrl and analytics.umamiAnalytics.replaysScriptUrl to your own script endpoints.

Image Optimization

PropertyTypeDefaultDescription
imageOptimization.formatsstring"webp"Output format: "avif", "webp", "both" (recommended)
imageOptimization.qualitynumber85Compression quality (1-100), recommended 70-85
imageOptimization.noReferrerDomainsstring[][]Domains requiring anti-hotlinking handling, supports wildcard *

WARNING

Astro can only optimize images in the src directory. More images means longer build times.

Anti-Hotlinking (Referrer Policy)

Some image hosts or CDNs (e.g. Bilibili CDN) enforce hotlink protection by checking the Referer request header, causing 403 errors when their images are embedded in your blog.

By configuring noReferrerDomains, Firefly will automatically add a referrerpolicy="no-referrer" attribute to <img> tags matching the specified domains, preventing the browser from sending the Referer header and bypassing hotlink protection.

ts
imageOptimization: {
  formats: "webp",
  quality: 85,
  noReferrerDomains: [
    "i0.hdslb.com",     // Bilibili CDN
    "i1.hdslb.com",
    "i2.hdslb.com",
    "*.bilibili.com",   // Wildcard support
  ],
},

TIP

  • Only applies to external images starting with http:// or https://, local images are not affected
  • Only affects <img> tags with matching domains, does not change referrer behavior for other links
  • Images with alt text in Markdown will still generate <figcaption> as expected