Refactor meta content, accept explicit nav title

This commit is contained in:
Sam Becker 2025-03-25 19:53:00 -05:00
parent 70a2cb3c37
commit 7f734659a3
7 changed files with 114 additions and 68 deletions

View File

@ -29,7 +29,7 @@ https://photos.sambecker.com
1. Click [Deploy](https://vercel.com/new/clone?demo-title=Photo+Blog&demo-description=Store+photos+with+original+camera+data&demo-url=https%3A%2F%2Fphotos.sambecker.com&demo-image=https%3A%2F%2Fphotos.sambecker.com%2Ftemplate-image-tight&project-name=Photo+Blog&repository-name=exif-photo-blog&repository-url=https%3A%2F%2Fgithub.com%2Fsambecker%2Fexif-photo-blog&from=templates&skippable-integrations=1&teamCreateStatus=hidden&stores=%5B%7B%22type%22%3A%22postgres%22%7D%2C%7B%22type%22%3A%22blob%22%7D%5D)
2. Add required storage ([Vercel Postgres](https://vercel.com/docs/storage/vercel-postgres/quickstart#create-a-postgres-database) + [Vercel Blob](https://vercel.com/docs/storage/vercel-blob/quickstart#create-a-blob-store)) as part of template installation
3. Configure environment variable for production domain in project settings
- `NEXT_PUBLIC_SITE_DOMAIN` (e.g., photos.domain.com—used in permalinks and seen in top-right nav)
- `NEXT_PUBLIC_SITE_DOMAIN` (e.g., photos.domain.com—used in absolute urls and seen in navigation if no explicit nav title is set)
### 2. Setup Auth
@ -105,9 +105,10 @@ _⚠ READ BEFORE PROCEEDING_
Application behavior can be changed by configuring the following environment variables:
#### Content
- `NEXT_PUBLIC_META_TITLE` (seen in browser tab)
- `NEXT_PUBLIC_META_DESCRIPTION` (seen in nav, beneath title)
- `NEXT_PUBLIC_NAV_TITLE` (seen in navigation, defaults to domain when not configured)
- `NEXT_PUBLIC_META_TITLE` (seen in search results and browser tab)
- `NEXT_PUBLIC_META_DESCRIPTION` (seen in search results)
- `NEXT_PUBLIC_NAV_TITLE` (defaults to domain when not configured)
- `NEXT_PUBLIC_NAV_CAPTION` (seen in navigation, beneath title)
- `NEXT_PUBLIC_PAGE_ABOUT` (seen in grid sidebar—accepts rich formatting tags: `<b>`, `<strong>`, `<i>`, `<em>`, `<u>`, `<br>`)
#### Performance

View File

@ -20,7 +20,6 @@ import { labelForStorage } from '@/platforms/storage';
import { HiSparkles } from 'react-icons/hi';
import { testConnectionsAction } from '@/admin/actions';
import ErrorNote from '@/components/ErrorNote';
import WarningNote from '@/components/WarningNote';
import { RiSpeedMiniLine } from 'react-icons/ri';
import SecretGenerator from '../app/SecretGenerator';
import { PiPaintBrushHousehold } from 'react-icons/pi';
@ -49,9 +48,11 @@ export default function AdminAppConfigurationClient({
hasAdminUser,
// Content
hasDomain,
hasTitle,
hasDescription,
hasAbout,
hasNavTitle,
hasNavCaption,
isMetaTitleConfigured,
isMetaDescriptionConfigured,
hasPageAbout,
// AI
isAiTextGenerationEnabled,
aiTextAutoGeneratedFields,
@ -153,20 +154,6 @@ export default function AdminAppConfigurationClient({
{message}
</ErrorNote>;
const renderWarning = ({
connection,
message,
}: {
connection?: { provider: string, error: string }
message?: string
}) =>
<WarningNote className="mt-2 mb-3">
{connection && <>
{connection.provider} connection error: {`"${connection.error}"`}
</>}
{message}
</WarningNote>;
return (
<ScoreCardContainer>
<ChecklistGroup
@ -302,40 +289,53 @@ export default function AdminAppConfigurationClient({
<ChecklistRow
title="Configure domain"
status={hasDomain}
showWarning
>
{!hasDomain &&
renderWarning({message:
'Not explicitly setting a domain may cause ' +
'certain features to behave unexpectedly',
})}
Store in environment variable (seen in top-right nav):
Store in environment variable
(used in explicit share urls, seen in nav if no title is defined):
{renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])}
</ChecklistRow>
<ChecklistRow
title="Add title"
status={hasTitle}
optional
title="Meta title"
status={isMetaTitleConfigured}
showWarning
>
Store in environment variable (seen in browser tab):
{renderEnvVars(['NEXT_PUBLIC_META_TITLE'])}
</ChecklistRow>
{!simplifiedView && <>
<ChecklistRow
title="Add description"
status={hasDescription}
title="Meta description"
status={isMetaDescriptionConfigured}
optional
>
Store in environment variable (seen in nav, under title):
Store in environment variable (seen in search results):
{renderEnvVars(['NEXT_PUBLIC_META_DESCRIPTION'])}
</ChecklistRow>
<ChecklistRow
title="Add about"
status={hasAbout}
title="Nav title"
status={hasNavTitle}
optional
>
Store in environment variable (seen in grid sidebar):
Store in environment variable (replaces domain in nav):
{renderEnvVars(['NEXT_PUBLIC_NAV_TITLE'])}
</ChecklistRow>
<ChecklistRow
title="Nav caption"
status={hasNavCaption}
optional
>
Store in environment variable (seen in nav, under title):
{renderEnvVars(['NEXT_PUBLIC_NAV_CAPTION'])}
</ChecklistRow>
<ChecklistRow
title="Page about"
status={hasPageAbout}
optional
>
Store in environment variable (seen in sidebar):
{renderEnvVars(['NEXT_PUBLIC_PAGE_ABOUT'])}
</ChecklistRow>
</>}
</ChecklistGroup>
{!simplifiedView && <>
<ChecklistGroup

View File

@ -13,6 +13,8 @@ import {
CATEGORY_VISIBILITY,
GRID_HOMEPAGE_ENABLED,
HAS_STATIC_OPTIMIZATION,
IS_META_TITLE_CONFIGURED,
IS_META_DESCRIPTION_CONFIGURED,
MATTE_PHOTOS,
} from '@/app/config';
import { getGitHubMetaForCurrentApp, getSignificantInsights } from '.';
@ -69,6 +71,9 @@ export default async function AdminAppInsights() {
noAi: !isAiTextGenerationEnabled,
noAiRateLimiting,
noConfiguredDomain,
noConfiguredMeta:
!IS_META_TITLE_CONFIGURED ||
!IS_META_DESCRIPTION_CONFIGURED,
outdatedPhotos,
photoMatting: photosCountPortrait > 0 && !MATTE_PHOTOS,
camerasFirst: (

View File

@ -45,6 +45,7 @@ import IconFilmSimulation from '@/components/icons/IconFilmSimulation';
import IconFocalLength from '@/components/icons/IconFocalLength';
import IconTag from '@/components/icons/IconTag';
import IconPhoto from '@/components/icons/IconPhoto';
import { HiOutlineDocumentText } from 'react-icons/hi';
const DEBUG_COMMIT_SHA = '4cd29ed';
const DEBUG_COMMIT_MESSAGE = 'Long commit message for debugging purposes';
@ -59,8 +60,8 @@ const readmeAnchor = (anchor: string) =>
README/{anchor}
</AdminLink>;
const renderLabeledEnvVar = (label: string, envVar: string, value = '1') =>
<div className="flex flex-col gap-1.5">
const renderLabeledEnvVar = (label: string, envVar: string, value?: string) =>
<div className="flex flex-col gap-0.5">
<span className="text-xs uppercase font-medium tracking-wider">
{label}
</span>
@ -112,6 +113,7 @@ export default function AdminAppInsightsClient({
noAi,
noAiRateLimiting,
noConfiguredDomain,
noConfiguredMeta,
outdatedPhotos,
photoMatting,
camerasFirst,
@ -277,7 +279,7 @@ export default function AdminAppInsightsClient({
!isExpanded,
)}
expandContent={<>
Not explicitly setting a domain may cause certain features
Not setting an explicit domain may cause certain features
to behave unexpectedly. Domains are stored in
{' '}
<EnvVar
@ -286,6 +288,28 @@ export default function AdminAppInsightsClient({
/>
</>}
/>}
{(noConfiguredMeta || debug) && <ScoreCardRow
icon={<HiOutlineDocumentText
size={18}
className="translate-x-[1px] translate-y-[-1px]"
/>}
content="Configure meta"
expandContent={<>
Configure site title (visible in search results and browser tab)
and site description (visible in search results):
{' '}
<div className="flex flex-col gap-y-4 mt-3">
{renderLabeledEnvVar(
'Site title',
'NEXT_PUBLIC_META_TITLE',
)}
{renderLabeledEnvVar(
'Site description',
'NEXT_PUBLIC_META_DESCRIPTION',
)}
</div>
</>}
/>}
{(noStaticOptimization || debug) && <ScoreCardRow
icon={<RiSpeedMiniLine
size={19}
@ -300,18 +324,22 @@ export default function AdminAppInsightsClient({
{renderLabeledEnvVar(
'Photo pages',
'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS',
'1',
)}
{renderLabeledEnvVar(
'Photo OG images',
'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES',
'1',
)}
{renderLabeledEnvVar(
'Category pages (tags, cameras, etc.)',
'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORIES',
'1',
)}
{renderLabeledEnvVar(
'Category OG images',
'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORY_OG_IMAGES',
'1',
)}
<span>
See {readmeAnchor('performance')} for cost implications.

View File

@ -20,6 +20,7 @@ const _INSIGHTS_TEMPLATE = [
'noAi',
'noAiRateLimiting',
'noConfiguredDomain',
'noConfiguredMeta',
'photoMatting',
'camerasFirst',
'gridFirst',

View File

@ -17,13 +17,12 @@ import {
import AnimateItems from '../components/AnimateItems';
import {
GRID_HOMEPAGE_ENABLED,
HAS_DEFINED_SITE_DESCRIPTION,
META_DESCRIPTION,
NAV_CAPTION,
} from './config';
import { useRef } from 'react';
import useStickyNav from './useStickyNav';
const NAV_HEIGHT_CLASS = HAS_DEFINED_SITE_DESCRIPTION
const NAV_HEIGHT_CLASS = NAV_CAPTION
? 'min-h-[4rem] sm:min-h-[5rem]'
: 'min-h-[4rem]';
@ -100,16 +99,16 @@ export default function Nav({
)}>
<div className={clsx(
'truncate overflow-hidden select-none',
HAS_DEFINED_SITE_DESCRIPTION && 'sm:font-bold',
NAV_CAPTION && 'sm:font-bold',
)}>
{renderLink(navTitleOrDomain, PATH_ROOT)}
</div>
{HAS_DEFINED_SITE_DESCRIPTION &&
{NAV_CAPTION &&
<div className={clsx(
'hidden sm:block truncate overflow-hidden',
'leading-tight',
)}>
{META_DESCRIPTION}
{NAV_CAPTION}
</div>}
</div>
</nav>]

View File

@ -92,6 +92,11 @@ const SITE_DOMAIN_SHORT = shortenUrl(SITE_DOMAIN);
export const NAV_TITLE =
process.env.NEXT_PUBLIC_NAV_TITLE;
export const NAV_CAPTION =
process.env.NEXT_PUBLIC_NAV_CAPTION ||
// Legacy environment variable
process.env.NEXT_PUBLIC_SITE_DESCRIPTION;
export const META_TITLE =
process.env.NEXT_PUBLIC_META_TITLE ||
// Legacy environment variable
@ -99,10 +104,19 @@ export const META_TITLE =
NAV_TITLE ||
TEMPLATE_TITLE;
export const IS_META_TITLE_CONFIGURED =
Boolean(process.env.NEXT_PUBLIC_META_TITLE) ||
// Legacy environment variable
Boolean(process.env.NEXT_PUBLIC_SITE_TITLE) ||
Boolean(NAV_TITLE);
export const IS_META_DESCRIPTION_CONFIGURED =
Boolean(process.env.NEXT_PUBLIC_META_DESCRIPTION) ||
Boolean(NAV_CAPTION);
export const META_DESCRIPTION =
process.env.NEXT_PUBLIC_META_DESCRIPTION ||
// Legacy environment variable
process.env.NEXT_PUBLIC_SITE_DESCRIPTION ||
NAV_CAPTION ||
SITE_DOMAIN;
export const NAV_TITLE_OR_DOMAIN =
@ -115,11 +129,6 @@ export const PAGE_ABOUT =
// Legacy environment variable
process.env.NEXT_PUBLIC_SITE_ABOUT;
export const HAS_DEFINED_SITE_DESCRIPTION =
Boolean(process.env.NEXT_PUBLIC_META_DESCRIPTION) ||
// Legacy environment variable
Boolean(process.env.NEXT_PUBLIC_SITE_DESCRIPTION);
// STORAGE
// STORAGE: DATABASE
@ -309,11 +318,14 @@ export const APP_CONFIGURATION = {
Boolean(process.env.ADMIN_EMAIL) &&
Boolean(process.env.ADMIN_PASSWORD)
),
// Content
// Domain
hasDomain: Boolean(process.env.NEXT_PUBLIC_SITE_DOMAIN),
hasTitle: Boolean(process.env.NEXT_PUBLIC_SITE_TITLE),
hasDescription: HAS_DEFINED_SITE_DESCRIPTION,
hasAbout: Boolean(process.env.NEXT_PUBLIC_SITE_ABOUT),
// Content
hasNavTitle: Boolean(NAV_TITLE),
hasNavCaption: Boolean(NAV_CAPTION),
isMetaTitleConfigured: IS_META_TITLE_CONFIGURED,
isMetaDescriptionConfigured: IS_META_DESCRIPTION_CONFIGURED,
hasPageAbout: Boolean(process.env.NEXT_PUBLIC_SITE_ABOUT),
// AI
isAiTextGenerationEnabled: AI_TEXT_GENERATION_ENABLED,
aiTextAutoGeneratedFields: process.env.AI_TEXT_AUTO_GENERATED_FIELDS