Refactor meta content, accept explicit nav title
This commit is contained in:
parent
70a2cb3c37
commit
7f734659a3
@ -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
|
||||
|
||||
@ -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>
|
||||
<ChecklistRow
|
||||
title="Add description"
|
||||
status={hasDescription}
|
||||
optional
|
||||
>
|
||||
Store in environment variable (seen in nav, under title):
|
||||
{renderEnvVars(['NEXT_PUBLIC_META_DESCRIPTION'])}
|
||||
</ChecklistRow>
|
||||
<ChecklistRow
|
||||
title="Add about"
|
||||
status={hasAbout}
|
||||
optional
|
||||
>
|
||||
Store in environment variable (seen in grid sidebar):
|
||||
{renderEnvVars(['NEXT_PUBLIC_PAGE_ABOUT'])}
|
||||
</ChecklistRow>
|
||||
{!simplifiedView && <>
|
||||
<ChecklistRow
|
||||
title="Meta description"
|
||||
status={isMetaDescriptionConfigured}
|
||||
optional
|
||||
>
|
||||
Store in environment variable (seen in search results):
|
||||
{renderEnvVars(['NEXT_PUBLIC_META_DESCRIPTION'])}
|
||||
</ChecklistRow>
|
||||
<ChecklistRow
|
||||
title="Nav title"
|
||||
status={hasNavTitle}
|
||||
optional
|
||||
>
|
||||
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
|
||||
|
||||
@ -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: (
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -20,6 +20,7 @@ const _INSIGHTS_TEMPLATE = [
|
||||
'noAi',
|
||||
'noAiRateLimiting',
|
||||
'noConfiguredDomain',
|
||||
'noConfiguredMeta',
|
||||
'photoMatting',
|
||||
'camerasFirst',
|
||||
'gridFirst',
|
||||
|
||||
@ -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>]
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user