feat: add SEO - #3
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughIntroduces site-wide SEO and fonts: adds SITE config, updates robots.txt, self-hosted Inter font, route-level head() metadata (OG/Twitter/canonical and JSON-LD), and an event route loader for server-side event data and dynamic metadata. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Route as EventRoute
participant ServerClient as serverHttpClient
participant DataStore as ExternalAPI
Client->>Route: GET /event/:slug
Route->>ServerClient: loader fetchEventBySlug(slug)
ServerClient->>DataStore: request event data
DataStore-->>ServerClient: return event payload
ServerClient-->>Route: loaderData (event)
Route-->>Client: render page + dynamic head/meta (title, description, OG, canonical)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/routes/__root.tsx`:
- Around line 58-67: Replace the external Google Fonts link tags (the elements
with href "https://fonts.googleapis.com/css2?family=Inter:..." and the
preconnects) in __root.tsx with references to a locally-served font bundle; add
Inter font files to the app (woff2/woff) and declare them via `@font-face` in your
global CSS (or a dedicated fonts.css), then import that CSS from the root (where
the current stylesheet link is added) and optionally add a local <link
rel="preload" as="font"> for the most-critical weights; update/remove the
crossOrigin preconnect entries and ensure the code paths that render the three
link objects (the objects with rel "preconnect" and rel "stylesheet") now point
to your local assets so the app self-hosts Inter instead of depending on
fonts.googleapis.com.
In `@apps/web/src/routes/event`.$slug.tsx:
- Around line 16-22: The loader currently swallows all exceptions when calling
httpClient.query(api.events.getEventBySlug, { slug: params.slug }) and returns
null; change the catch block to log or report the caught error (e.g.,
console.error or your app error reporter/Sentry) including the error object and
context (slug) before returning the fallback, so failures during SSR/backend
calls are visible for debugging; keep the return null fallback but ensure the
catch in the event.$slug route exposes the error via logging/reporting.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
apps/web/public/robots.txtapps/web/src/lib/seo.tsapps/web/src/routes/__root.tsxapps/web/src/routes/dashboard.tsxapps/web/src/routes/event.$slug.tsxapps/web/src/routes/feed.tsxapps/web/src/routes/index.tsxapps/web/src/routes/unsubscribe.tsx
| { rel: "preconnect", href: "https://fonts.googleapis.com" }, | ||
| { | ||
| rel: "preconnect", | ||
| href: "https://fonts.gstatic.com", | ||
| crossOrigin: "anonymous", | ||
| }, | ||
| { | ||
| rel: "stylesheet", | ||
| href: appCss, | ||
| href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap", | ||
| }, |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider self-hosting Inter to reduce third-party critical-path cost.
Line 66 introduces a render-path dependency on Google Fonts. Self-hosting Inter (or bundling via local @font-face) usually improves first paint consistency and removes third-party dependency from initial render.
As per coding guidelines, "apps/web/**: - Focus on web performance and accessibility".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/routes/__root.tsx` around lines 58 - 67, Replace the external
Google Fonts link tags (the elements with href
"https://fonts.googleapis.com/css2?family=Inter:..." and the preconnects) in
__root.tsx with references to a locally-served font bundle; add Inter font files
to the app (woff2/woff) and declare them via `@font-face` in your global CSS (or a
dedicated fonts.css), then import that CSS from the root (where the current
stylesheet link is added) and optionally add a local <link rel="preload"
as="font"> for the most-critical weights; update/remove the crossOrigin
preconnect entries and ensure the code paths that render the three link objects
(the objects with rel "preconnect" and rel "stylesheet") now point to your local
assets so the app self-hosts Inter instead of depending on fonts.googleapis.com.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/index.css`:
- Line 10: The `@font-face` declarations use quoted single-word font family names
(e.g., "Inter") which violates the stylelint rule; update the font-family
properties inside each `@font-face` block (look for font-family in the file) to
remove the quotes so they read Inter (and likewise for any other single-word
names on the other occurrence), keeping spacing and capitalization unchanged.
In `@apps/web/src/routes/event`.$slug.tsx:
- Around line 44-45: When constructing canonical and og:url metadata in
event.$slug.tsx, encode params.slug to avoid invalid URLs; update the places
that build `${SITE.url}/event/${params.slug}` (including the og:url and
canonical link construction) to use an encoded slug (e.g., via
encodeURIComponent(params.slug)) so reserved characters are percent-encoded and
URLs remain valid.
- Around line 38-51: The route's meta array for event.$slug.tsx adds og:* and
twitter:image but omits route-specific twitter:title and twitter:description, so
add { name: "twitter:title", content: title } and { name: "twitter:description",
content: description } into the meta array (near the existing
og:title/og:description entries and alongside the conditional twitter:image) so
that Twitter cards use the event's title and description; reference the meta
array, the title and description variables, and the imageUrl conditional when
inserting these tags.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
apps/web/public/fonts/inter-latin-ext-wght-normal.woff2is excluded by!**/*.woff2apps/web/public/fonts/inter-latin-wght-normal.woff2is excluded by!**/*.woff2
📒 Files selected for processing (5)
apps/web/DESIGN_SYSTEM.mdapps/web/src/index.cssapps/web/src/routes/__root.tsxapps/web/src/routes/dashboard.tsxapps/web/src/routes/event.$slug.tsx
| Self-hosted Inter variable font (latin + latin-ext) | ||
| ────────────────────────────────────────────── */ | ||
| @font-face { | ||
| font-family: "Inter"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if the file exists and view content around the reported lines
cat -n apps/web/src/index.css | head -35Repository: flvvius/news
Length of output: 1487
🏁 Script executed:
# Search for stylelint configuration files
fd -e '.stylelintrc' -e 'stylelint.config' -e '.stylelintignore' .Repository: flvvius/news
Length of output: 38
🏁 Script executed:
# Check package.json for stylelint configuration
find . -name "package.json" -type f | xargs grep -l "stylelint" | head -5Repository: flvvius/news
Length of output: 38
🏁 Script executed:
# Search for package.json and check for stylelint
find . -maxdepth 3 -name "package.json" | head -3 | xargs cat | jq '.devDependencies, .stylelint' 2>/dev/nullRepository: flvvius/news
Length of output: 505
🏁 Script executed:
# Check root directory for stylelint config
ls -la . | grep -i stylelintRepository: flvvius/news
Length of output: 124
🏁 Script executed:
# Search for any stylelint configuration in common locations
cat .stylelintrc.json 2>/dev/null || cat .stylelintrc.js 2>/dev/null || cat stylelint.config.js 2>/dev/null || echo "No stylelint config found in root"Repository: flvvius/news
Length of output: 1062
🏁 Script executed:
# Check the web app's specific package.json for stylelint
cat apps/web/package.json | jq '.devDependencies' 2>/dev/null | grep -i stylelintRepository: flvvius/news
Length of output: 38
🏁 Script executed:
# Search for stylelint in any package.json files
find . -name "package.json" -type f -exec grep -l "stylelint" {} \;Repository: flvvius/news
Length of output: 38
🏁 Script executed:
# Check the root package.json more thoroughly
cat package.json | jq '.devDependencies, .dependencies' 2>/dev/null | head -50Repository: flvvius/news
Length of output: 82
🌐 Web query:
stylelint-config-standard-scss font-family-name-quotes default rule
💡 Result:
In stylelint-config-standard-scss, font-family-name-quotes defaults to:
"always-where-recommended"(inherited fromstylelint-config-standard; it’s not overridden instylelint-config-standard-scss). [1]
What that option means: it requires quotes only where the CSS spec recommends them (e.g., names with spaces/digits/punctuation) and disallows quotes otherwise. [2]
Sources
[1] stylelint-config-standard config (font-family-name-quotes: 'always-where-recommended') — (raw.githubusercontent.com)
[2] Stylelint rule docs for font-family-name-quotes option "always-where-recommended" — (stylelint.io)
Citations:
- 1: https://raw.githubusercontent.com/stylelint/stylelint-config-standard/main/index.js
- 2: https://stylelint.io/user-guide/rules/font-family-name-quotes/?utm_source=openai
Remove quotes around font-family names in @font-face blocks.
Lines 10 and 21 violate the stylelint rule font-family-name-quotes: always-where-recommended. Since "Inter" is a single-word name requiring no quotes per the CSS spec, the quotes must be removed.
Proposed fix
`@font-face` {
- font-family: "Inter";
+ font-family: Inter;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/fonts/inter-latin-wght-normal.woff2") format("woff2");
}
`@font-face` {
- font-family: "Inter";
+ font-family: Inter;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/fonts/inter-latin-ext-wght-normal.woff2") format("woff2");
}Also applies to: 21-21
🧰 Tools
🪛 Stylelint (17.3.0)
[error] 10-10: Unexpected quotes around "Inter" (font-family-name-quotes)
(font-family-name-quotes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/index.css` at line 10, The `@font-face` declarations use quoted
single-word font family names (e.g., "Inter") which violates the stylelint rule;
update the font-family properties inside each `@font-face` block (look for
font-family in the file) to remove the quotes so they read Inter (and likewise
for any other single-word names on the other occurrence), keeping spacing and
capitalization unchanged.
| { property: "og:url", content: `${SITE.url}/event/${params.slug}` }, | ||
| ...(imageUrl |
There was a problem hiding this comment.
Encode slug when building canonical and og:url metadata.
If params.slug contains reserved characters, these URLs can become invalid or inconsistent.
♻️ Proposed fix
+ const encodedSlug = encodeURIComponent(params.slug);
return {
meta: [
{ title },
{ name: "description", content: description },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:type", content: "article" },
- { property: "og:url", content: `${SITE.url}/event/${params.slug}` },
+ { property: "og:url", content: `${SITE.url}/event/${encodedSlug}` },
...(imageUrl
? [
{ property: "og:image", content: imageUrl },
{ name: "twitter:image", content: imageUrl },
]
: []),
],
- links: [{ rel: "canonical", href: `${SITE.url}/event/${params.slug}` }],
+ links: [{ rel: "canonical", href: `${SITE.url}/event/${encodedSlug}` }],
};Also applies to: 52-53
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/routes/event`.$slug.tsx around lines 44 - 45, When constructing
canonical and og:url metadata in event.$slug.tsx, encode params.slug to avoid
invalid URLs; update the places that build `${SITE.url}/event/${params.slug}`
(including the og:url and canonical link construction) to use an encoded slug
(e.g., via encodeURIComponent(params.slug)) so reserved characters are
percent-encoded and URLs remain valid.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/web/src/routes/event.$slug.tsx (1)
44-45:⚠️ Potential issue | 🟡 MinorEncode
params.sluginog:urland canonical URLs.Line 44 and Line 54 still use raw
params.slug; reserved characters can produce invalid/inconsistent URLs.♻️ Proposed fix
head: ({ loaderData, params }) => { + const encodedSlug = encodeURIComponent(params.slug); const title = loaderData?.event?.title ? `${loaderData.event.title} — ${SITE.name}` : `Event — ${SITE.name}`; @@ - { property: "og:url", content: `${SITE.url}/event/${params.slug}` }, + { property: "og:url", content: `${SITE.url}/event/${encodedSlug}` }, @@ - links: [{ rel: "canonical", href: `${SITE.url}/event/${params.slug}` }], + links: [{ rel: "canonical", href: `${SITE.url}/event/${encodedSlug}` }], }; },Also applies to: 54-54
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/event`.$slug.tsx around lines 44 - 45, The og:url and canonical URL are currently built using raw params.slug which can contain reserved characters; update the places that set the meta property { property: "og:url", content: `${SITE.url}/event/${params.slug}` } and the canonical link (the link with rel="canonical") to encode the slug using encodeURIComponent(params.slug) when composing the URL so both meta tags produce valid, safe URLs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/web/src/routes/event`.$slug.tsx:
- Around line 44-45: The og:url and canonical URL are currently built using raw
params.slug which can contain reserved characters; update the places that set
the meta property { property: "og:url", content:
`${SITE.url}/event/${params.slug}` } and the canonical link (the link with
rel="canonical") to encode the slug using encodeURIComponent(params.slug) when
composing the URL so both meta tags produce valid, safe URLs.
Summary by CodeRabbit
New Features
Chores
Documentation