Skip to content

feat: fixes for SEO/GEO#400

Open
iatopilskii wants to merge 1 commit intomainfrom
feat/seo-fixes
Open

feat: fixes for SEO/GEO#400
iatopilskii wants to merge 1 commit intomainfrom
feat/seo-fixes

Conversation

@iatopilskii
Copy link
Contributor

@iatopilskii iatopilskii commented Mar 17, 2026

Summary

SEO audit fixes — Round 2 based on SquirrelScan 730-page audit. Targets list structure, insecure URLs, and focus visibility issues in the Gatsby site.

Round 1 (already merged)

  • Broken blog category links (trailing slash)
  • Multiple H1 on blog pages (hero h1→h2)
  • CSP + Feature-Policy headers
  • sw.js 404 (unregister script)

Round 2 (this PR)

  • List structure: Wrap social <Link> elements in <li> inside footer <ul> — they were direct children without list item wrappers (fixes 518 pages)
  • HTTP → HTTPS URLs: Change 3 hardcoded http:// URLs to https://go.novu.co/dashboard (2 occurrences) and Amazon.com link in email template
  • Focus visible: Scope .tippy-box * { outline: none !important } to exclude :focus-visible elements, preserving keyboard focus indicators

Files changed

List structure

  • src/components/shared/footer/footer.jsx — social links wrapped in <li>

HTTP → HTTPS

  • src/data/pages/inbox/index.jsxhttp://go.novu.co/dashboardhttps:// (2 occurrences)
  • src/components/pages/home/email-editor/data/email-content.jshttp://Amazon.comhttps://

Focus visible

  • src/styles/maily.css**:not(:focus-visible) in .tippy-box outline override

Summary by CodeRabbit

  • Security

    • Added advanced security policy headers for enhanced client-side protection.
    • Upgraded external links to secure HTTPS protocol.
  • Accessibility

    • Improved focus-state styling for keyboard navigation.
    • Restructured footer navigation for better semantic markup.
  • Infrastructure

    • Optimized service worker cleanup for improved app refresh behavior.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

This PR applies several targeted improvements: adding HTTP security policy headers via Netlify configuration, standardizing URLs to HTTPS, updating the blog route with a trailing slash, restructuring footer social link markup, adjusting a blog heading level, refining focus-visibility styling, and introducing a service worker for cleanup.

Changes

Cohort / File(s) Summary
Security & Configuration
gatsby-config.js, static/sw.js
Added Content-Security-Policy-Report-Only and Feature-Policy headers to Netlify config; added service worker script to unregister previously installed workers on install/activation.
URL Standardization
src/constants/links.js, src/components/pages/home/email-editor/data/email-content.js, src/data/pages/inbox/index.jsx
Converted HTTP links to HTTPS across email content and inbox data; updated blog route from /blog to /blog/ for trailing-slash consistency.
Component Updates
src/components/pages/blog/hero/hero.jsx, src/components/shared/footer/footer.jsx
Changed blog hero title from h1 to h2 heading level; restructured footer social links from direct Link→SVG to Link→li→SVG wrapper structure.
Focus Styling
src/styles/maily.css
Modified tippy-box outline rule to apply only when not focus-visible, refining keyboard accessibility focus indication.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

Suggested reviewers

  • scopsy

Poem

🐰 A rabbit hops through links so bright,
Converting each to HTTPS light,
Headers shield, footers reorganize,
The blog route now more standardized—
Small tweaks that polish up the site! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: fixes for SEO/GEO' directly and accurately summarizes the main changes—implementing SEO audit fixes including list structure corrections, HTTPS URL upgrades, and accessibility improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/seo-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
gatsby-config.js (1)

205-205: Remove the deprecated Feature-Policy header (line 205).

Both Permissions-Policy (line 202) and Feature-Policy (line 205) are present and configure identical restrictions for camera, microphone, and geolocation. The Feature-Policy header is deprecated and should be removed; Permissions-Policy is the modern standard and takes precedence when both are present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gatsby-config.js` at line 205, Remove the deprecated Feature-Policy header
entry by deleting the string "Feature-Policy: camera 'none'; microphone 'none';
geolocation 'none'" from the headers list in gatsby-config.js so only the modern
"Permissions-Policy" header remains (it's the duplicate at/near the
Permissions-Policy entry); ensure no other occurrences of "Feature-Policy"
remain in the headers array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@static/sw.js`:
- Around line 3-7: The activate handler currently doesn't accept the event param
or use event.waitUntil, and it uses forEach which ignores the promises from
client.navigate; change the listener to accept the event (e.g., (event) => { ...
}) and call event.waitUntil(...) with a promise chain that awaits
self.clients.matchAll({type: 'window'}) then maps clients to
client.navigate(client.url), uses Promise.all(...) to wait for all navigations,
and finally chains self.registration.unregister() so unregister runs after
navigations complete; reference the existing addEventListener('activate'),
self.clients.matchAll, client.navigate, event.waitUntil, and
self.registration.unregister when making this change.

---

Nitpick comments:
In `@gatsby-config.js`:
- Line 205: Remove the deprecated Feature-Policy header entry by deleting the
string "Feature-Policy: camera 'none'; microphone 'none'; geolocation 'none'"
from the headers list in gatsby-config.js so only the modern
"Permissions-Policy" header remains (it's the duplicate at/near the
Permissions-Policy entry); ensure no other occurrences of "Feature-Policy"
remain in the headers array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 645f6aab-75f7-44a1-8025-f32710ea6464

📥 Commits

Reviewing files that changed from the base of the PR and between 4d824e9 and a2d22ee.

📒 Files selected for processing (8)
  • gatsby-config.js
  • src/components/pages/blog/hero/hero.jsx
  • src/components/pages/home/email-editor/data/email-content.js
  • src/components/shared/footer/footer.jsx
  • src/constants/links.js
  • src/data/pages/inbox/index.jsx
  • src/styles/maily.css
  • static/sw.js

Comment on lines +3 to +7
self.addEventListener('activate', () => {
self.clients.matchAll({ type: 'window' }).then((clients) => {
clients.forEach((client) => client.navigate(client.url));
});
self.registration.unregister();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify lifecycle handling and async patterns in service worker
rg -n "addEventListener\\('install'|addEventListener\\('activate'|waitUntil|forEach\\(|map\\(|navigate\\(|unregister\\(" static/sw.js

Repository: novuhq/website

Length of output: 263


🏁 Script executed:

cat -n static/sw.js

Repository: novuhq/website

Length of output: 439


activate event handler must use event.waitUntil() to delay termination during cleanup.

The handler doesn't extend the service worker's lifetime for async operations. Without event.waitUntil(), the browser may terminate the service worker before matchAll(), navigate(), and unregister() complete. Additionally, forEach() ignores promises returned by client.navigate() calls, so the async operations aren't properly awaited. The unregister() call executes immediately without waiting for all navigations to finish.

Use map() + Promise.all() to aggregate promises from all navigate() calls, and chain unregister() to execute after navigations complete—all wrapped in event.waitUntil().

🔧 Suggested fix
-self.addEventListener('activate', () => {
-  self.clients.matchAll({ type: 'window' }).then((clients) => {
-    clients.forEach((client) => client.navigate(client.url));
-  });
-  self.registration.unregister();
-});
+self.addEventListener('activate', (event) => {
+  event.waitUntil(
+    self.clients
+      .matchAll({ type: 'window' })
+      .then((clients) => Promise.all(clients.map((client) => client.navigate(client.url))))
+      .then(() => self.registration.unregister())
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.addEventListener('activate', () => {
self.clients.matchAll({ type: 'window' }).then((clients) => {
clients.forEach((client) => client.navigate(client.url));
});
self.registration.unregister();
self.addEventListener('activate', (event) => {
event.waitUntil(
self.clients
.matchAll({ type: 'window' })
.then((clients) => Promise.all(clients.map((client) => client.navigate(client.url))))
.then(() => self.registration.unregister())
);
});
🧰 Tools
🪛 Biome (2.4.7)

[error] 5-5: This callback passed to forEach() iterable method should not return a value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@static/sw.js` around lines 3 - 7, The activate handler currently doesn't
accept the event param or use event.waitUntil, and it uses forEach which ignores
the promises from client.navigate; change the listener to accept the event
(e.g., (event) => { ... }) and call event.waitUntil(...) with a promise chain
that awaits self.clients.matchAll({type: 'window'}) then maps clients to
client.navigate(client.url), uses Promise.all(...) to wait for all navigations,
and finally chains self.registration.unregister() so unregister runs after
navigations complete; reference the existing addEventListener('activate'),
self.clients.matchAll, client.navigate, event.waitUntil, and
self.registration.unregister when making this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant