-
Notifications
You must be signed in to change notification settings - Fork 469
add free tools #723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add free tools #723
Conversation
…cture - Added new tool pages to the sitemap, including UTM Builder, SEO Title Generator, and Privacy Policy Builder, enhancing SEO and discoverability. - Updated the sitemap function to return a comprehensive list of tool pages alongside existing static and dynamic pages. - Refactored the layout of the sitemap for better organization and maintainability.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded@goldflag has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 26 minutes and 3 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (15)
WalkthroughMany interactive tool UIs were extracted into standalone client form components and reused by new ToolPageLayout-based pages which add metadata and JSON‑LD; Breadcrumbs, RelatedTools, ToolCTA, sitemap, and several docs were added; the AI Privacy Policy Generator page was removed and minor styling/gits changes applied. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ToolPage
participant ToolPageLayout
participant FormComponent
participant API
User->>ToolPage: Navigate to /tools/{slug}
ToolPage->>ToolPageLayout: Render layout + structuredData
ToolPageLayout->>ToolPageLayout: Render Breadcrumbs, Header, RelatedTools, CTA
ToolPageLayout->>FormComponent: Mount tool component
User->>FormComponent: Provide inputs / Submit
FormComponent->>API: POST /api/tools/{endpoint} (if applicable)
API-->>FormComponent: Response (+ X-RateLimit-Remaining)
alt success
FormComponent->>User: Show results (cards/charts/text)
else failure
FormComponent->>User: Show error alert
end
User->>FormComponent: Copy / Download / Clear
FormComponent->>User: Update UI feedback
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Nitpick comments (28)
docs/src/app/sitemap.ts (1)
24-40: Consider dynamically generating the tool list to reduce maintenance burden.Verification confirms all 14 hardcoded tool slugs have corresponding page files at
docs/src/app/(home)/tools/{slug}/page.tsx, and no orphaned tools exist. However, the hardcoding creates a maintenance anti-pattern: any new tool must be manually added to this array or it won't appear in the sitemap, risking missed SEO benefits.Dynamic discovery would eliminate this risk and ensure new tools are automatically included.
docs/src/app/(home)/tools/meta-description-generator/MetaDescriptionForm.tsx (1)
57-61: Add error handling for clipboard operations.The
navigator.clipboard.writeText()call can fail if the user denies permission or if the page is not in a secure context (HTTPS). Consider adding a try-catch block to handle failures gracefully.Apply this diff:
const copyDescription = async (description: string, index: number) => { - await navigator.clipboard.writeText(description); - setCopiedIndex(index); - setTimeout(() => setCopiedIndex(null), 2000); + try { + await navigator.clipboard.writeText(description); + setCopiedIndex(index); + setTimeout(() => setCopiedIndex(null), 2000); + } catch (err) { + // Fallback: could show an error message or use a different copy method + console.error('Failed to copy to clipboard:', err); + } };docs/src/app/(home)/tools/privacy-policy-builder/PrivacyPolicyBuilderForm.tsx (1)
129-135: Add error handling for clipboard operations.Similar to other tools in this PR, the clipboard operation lacks error handling. Consider wrapping it in a try-catch block.
Apply this diff:
const copyToClipboard = async () => { if (policy) { - await navigator.clipboard.writeText(policy); - setCopied(true); - setTimeout(() => setCopied(false), 2000); + try { + await navigator.clipboard.writeText(policy); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy to clipboard:', err); + } } };docs/src/app/(home)/tools/funnel-visualizer/FunnelVisualizerForm.tsx (1)
7-18: Consider renaming interfaces to avoid naming collision.The
FunnelStepandFunnelChartDatatypes defined here have the same names as types inclient/src/api/analytics/funnels/useGetFunnel.ts, but with completely different structures. While they're in different packages (docs vs. client), this creates confusion and makes the codebase harder to maintain.Consider prefixing these with a namespace or descriptive qualifier:
-interface FunnelStep { +interface ToolFunnelStep { name: string; visitors: string; } -interface FunnelChartData { +interface ToolFunnelChartData { stepName: string; visitors: number; conversionRate: number; dropoffRate: number; stepNumber: number; }Then update the usages:
-const [steps, setSteps] = useState<FunnelStep[]>([ +const [steps, setSteps] = useState<ToolFunnelStep[]>([ // ... ]); -const calculateFunnelData = (): FunnelChartData[] | null => { +const calculateFunnelData = (): ToolFunnelChartData[] | null => { // ... };docs/src/app/(home)/tools/bounce-rate-calculator/BounceRateForm.tsx (1)
22-27: Handle 0 bounced sessions and numeric validation more explicitly
calculateBounceRatetreats0as falsy, so a valid case likebouncedSessions = "0"never shows a0%bounce rate, and negative values aren’t rejected explicitly.Consider guarding for
NaN/bounds instead of generic falsy checks:- const calculateBounceRate = () => { - const total = parseFloat(totalSessions); - const bounced = parseFloat(bouncedSessions); - if (!total || !bounced || total === 0) return null; - return (bounced / total) * 100; - }; + const calculateBounceRate = () => { + const total = parseFloat(totalSessions); + const bounced = parseFloat(bouncedSessions); + + if ( + Number.isNaN(total) || + Number.isNaN(bounced) || + total <= 0 || + bounced < 0 || + bounced > total + ) { + return null; + } + + return (bounced / total) * 100; + };docs/src/components/RelatedTools.tsx (1)
11-115: Related tools logic looks solid; consider future data extractionFiltering out the current tool, reordering by category, and slicing by
maxToolsis clean and easy to follow. If this catalog grows, you may eventually want to moveallToolsinto a shared config/data module so non-UI changes (adding tools, categories) don’t require touching this component.docs/src/app/(home)/tools/ai-privacy-policy-generator/PrivacyPolicyForm.tsx (1)
56-60: Harden clipboard copy against API failures
copyToClipboardassumesnavigator.clipboard.writeTextalways exists and succeeds; in older browsers or restricted contexts this can reject, causing an unhandled promise.Wrapping in try/catch and surfacing an error (or silently no-op) would make this more robust:
- const copyToClipboard = async () => { - await navigator.clipboard.writeText(generatedPolicy); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; + const copyToClipboard = async () => { + try { + await navigator.clipboard.writeText(generatedPolicy); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // Optionally: setError("Unable to copy to clipboard"); + } + };docs/src/app/(home)/tools/traffic-value-calculator/TrafficValueForm.tsx (1)
13-27: Tighten numeric validation and reuse validated inputs for calculations
calculateValuecurrently uses falsy checks, so0values (e.g. 0% conversion rate) are treated as invalid, while negative numbers pass through. The 1% improvement calculation also reparses raw state rather than reusing validated inputs.You can make this more robust and self-consistent by explicitly checking for
NaNand bounds:- const calculateValue = () => { - const visitors = parseFloat(monthlyVisitors); - const cr = parseFloat(conversionRate) / 100; - const aov = parseFloat(averageOrderValue); - const margin = parseFloat(profitMargin) / 100; - - if (!visitors || !cr || !aov || !margin) return null; + const calculateValue = () => { + const visitors = parseFloat(monthlyVisitors); + const crPercent = parseFloat(conversionRate); + const aov = parseFloat(averageOrderValue); + const marginPercent = parseFloat(profitMargin); + + if ( + Number.isNaN(visitors) || + Number.isNaN(crPercent) || + Number.isNaN(aov) || + Number.isNaN(marginPercent) || + visitors <= 0 || + crPercent < 0 || + aov <= 0 || + marginPercent < 0 + ) { + return null; + } + + const cr = crPercent / 100; + const margin = marginPercent / 100; @@ - <li> - • A <strong>1% conversion rate improvement</strong> would add{" "} - <strong> - $ - {( - (parseFloat(monthlyVisitors) * - 0.01 * - parseFloat(averageOrderValue) * - parseFloat(profitMargin)) / - 100 - ).toLocaleString(undefined, { maximumFractionDigits: 0 })} - /month - </strong>{" "} - in profit - </li> + <li> + • A <strong>1% conversion rate improvement</strong> would add{" "} + <strong> + $ + {( + visitors * 0.01 * aov * margin + ).toLocaleString(undefined, { maximumFractionDigits: 0 })} + /month + </strong>{" "} + in profit + </li>This preserves behavior for valid inputs, allows zero-rate scenarios to render as zero value, and keeps all calculations based on the same validated numbers.
Also applies to: 196-205
docs/src/app/(home)/tools/marketing-roi-calculator/MarketingROIForm.tsx (1)
17-29: Use explicit numeric validation instead of generic falsy checks incalculateMetrics
if (!spend || !rev || spend === 0)hides legitimate cases likerevenue = 0(which could still have a well-defined, very negative ROI) while allowing negative inputs to slip through.You can make validation clearer and less error-prone by checking for
NaNand bounds directly:- const calculateMetrics = (): Metrics | null => { - const spend = parseFloat(adSpend); - const rev = parseFloat(revenue); - const cogs = parseFloat(costOfGoodsSold) || 0; - - if (!spend || !rev || spend === 0) return null; + const calculateMetrics = (): Metrics | null => { + const spend = parseFloat(adSpend); + const rev = parseFloat(revenue); + const cogs = parseFloat(costOfGoodsSold) || 0; + + if ( + Number.isNaN(spend) || + Number.isNaN(rev) || + spend <= 0 || + rev <= 0 + ) { + return null; + }This keeps the downstream math unchanged while making the accepted input domain explicit.
docs/src/app/(home)/tools/ctr-calculator/page.tsx (2)
45-113: Align JSON-LD FAQ entries with visible FAQ contentThe
FAQPage.mainEntityincludes questions/answers (e.g., “Why is my CTR different from competitor benchmarks?”) that don’t appear as visible Q&A on the page, while some visible FAQ items aren’t in the JSON-LD. For better compliance with search guidelines and to avoid confusing crawlers, consider either:
- mirroring the visible FAQ items exactly in
structuredData, or- adding corresponding visible Q&A content for every JSON-LD FAQ entry.
Also applies to: 398-465
115-119: JSON-LD script usesdangerouslySetInnerHTML(lint noise vs. practical need)Using
dangerouslySetInnerHTMLhere is the standard way to emit JSON-LD and is safe as long asstructuredDatastays fully static / not user‑influenced. If you want to appease the security lints, consider either:
- centralizing this into a small helper component where the lint rule is locally disabled, or
- adding an inline rule suppression just for this script tag with a brief comment that the data is static JSON-LD.
docs/src/app/(home)/tools/utm-builder/UTMBuilderForm.tsx (1)
17-31: Improve feedback for invalid URLs and clipboard failuresRight now, if
new URL(...)throws you just return"", so the “Your UTM URL” section never appears and the user gets no explanation. Similarly, a rejectednavigator.clipboard.writeTextwill throw and never togglecopied.Consider:
- Tracking a small
errorstate to surface “Please enter a valid URL” when thetryblock fails.- Wrapping
copyToClipboardin atry/catchand optionally showing a brief error/toast if clipboard access fails (or at least swallowing the error to avoid noisy console logs).These are UX polish items, but they’ll make the tool more self‑explanatory.
Also applies to: 33-39, 141-161
docs/src/app/(home)/tools/seo-title-generator/SEOTitleForm.tsx (1)
24-33: Tighten validation and clipboard robustness for title generationMinor UX improvements you might consider:
- Disable the “Generate SEO Titles” button when
topicis empty (e.g.,disabled={isLoading || !topic.trim()}) so the user can’t trigger a no‑op request and only then see an error.- As with the other forms, wrap
navigator.clipboard.writeTextin atry/catchto avoid unhandled rejections if clipboard access fails.These changes are optional but would make the interaction a bit more resilient.
Also applies to: 58-62, 117-130
docs/src/app/(home)/tools/sample-size-calculator/page.tsx (2)
1-12: Useimport typeforMetadataand double‑check canonical domainTo keep
Metadatastrictly type‑only and match the pattern in other pages (e.g., the CTR calculator), consider:-import { Metadata } from "next"; +import type { Metadata } from "next";Also, this page lives under the docs app tree but sets canonical/OpenGraph URLs to
https://rybbit.com/tools/sample-size-calculator. If that’s intentionally canonicalizing docs → main marketing domain, all good; otherwise it’s worth verifying the target domain is correct.
124-128: JSON-LD script usesdangerouslySetInnerHTML(same pattern as CTR page)As with the CTR page, using
dangerouslySetInnerHTMLfor static JSON-LD is the standard approach and safe as long asstructuredDataremains hard‑coded and not user‑derived. If you want to quiet the security lints, you can:
- isolate this into a tiny JSON-LD helper component with a local lint suppression, or
- add a one-line suppression + comment explaining the data is static.
docs/src/app/(home)/tools/analytics-detector/page.tsx (1)
43-116: HandledangerouslySetInnerHTML+ lint warnings via a shared JSON‑LD helper or inline ignoreThe JSON‑LD is static and not user‑controlled, so using
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />is the standard pattern here, but Biome flags it. Consider either:
- Extracting a
<JsonLd data={jsonLd} />helper that encapsulates this pattern and carries a one‑time/* biome-ignore lint/security/noDangerouslySetInnerHtml */, or- Adding that ignore comment directly above this
<script>tag.That keeps XSS risk low while satisfying lint rules across all tool pages using the same approach.
docs/src/app/(home)/tools/utm-builder/page.tsx (1)
43-115: Refactor JSON‑LD injection to central helper or annotate for BiomeAs with the analytics detector page, this uses the standard JSON‑LD pattern with
dangerouslySetInnerHTMLand staticstructuredData. To keep Biome happy and centralize XSS considerations, you could:// Example helper component /* biome-ignore lint/security/noDangerouslySetInnerHtml */ function JsonLd({ data }: { data: unknown }) { return ( <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} /> ); }and then use
<JsonLd data={structuredData} />here and on other tool pages.docs/src/app/(home)/tools/og-tag-generator/OGTagForm.tsx (1)
60-64: Guard against clipboard API failures incopyCode
navigator.clipboard.writeTextcan fail (HTTP not HTTPS, browser restrictions, denied permissions), which would currently result in an unhandled promise rejection. Consider wrapping this in try/catch and optionally surfacing an error message:const copyCode = async (code: string, index: number) => { try { await navigator.clipboard.writeText(code); setCopiedIndex(index); setTimeout(() => setCopiedIndex(null), 2000); } catch { setError("Unable to copy to clipboard. Please copy manually."); } };This makes the UX more robust on stricter environments.
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx (1)
45-117: Apply the shared JSON‑LD helper or lint ignore here as wellThis page repeats the same static JSON‑LD +
dangerouslySetInnerHTMLpattern. Once you introduce a shared<JsonLd />helper or add a Biome ignore near this script, please reuse that approach here to keep lint output clean and centralize any future security review.docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (1)
42-113: Unify JSON‑LD injection approach and addressdangerouslySetInnerHTMLlintAs with the other tool pages, this uses static structured data plus
dangerouslySetInnerHTML. To avoid repeated Biome warnings while keeping this standard pattern, please either:
- Use a shared
<JsonLd data={structuredData} />helper that encapsulates the script tag and carries a targeted/* biome-ignore lint/security/noDangerouslySetInnerHtml */, or- Add that ignore comment near this script and document that only static, non‑user data is injected.
That keeps things explicit from a security-review standpoint.
docs/src/app/(home)/tools/page-speed-calculator/page.tsx (1)
48-120: JSON-LD injection viadangerouslySetInnerHTMLis safe here but will keep lint failingYou’re stringifying a fully static
schemaobject and injecting it as JSON-LD. Since there’s no user input, practical XSS risk is negligible, but the security lints will continue to complain.Two options:
- Keep as-is and explicitly disable/adjust the
noDangerouslySetInnerHtmlrule for this line/pattern, or- Extract a small
<JsonLd data={schema} />helper (or usenext/script) where you centralize thedangerouslySetInnerHTMLusage and lint override.Either way, note that any future changes must keep
schemafree of user-controlled data.docs/src/app/(home)/tools/privacy-policy-builder/page.tsx (1)
40-115: Static JSON-LD +dangerouslySetInnerHTML: safe pattern but triggers security lintAs with the other tools,
structuredDatais a static object and is only serialized viaJSON.stringify, so there’s no realistic XSS path. However, linters flag the directdangerouslySetInnerHTMLusage.Consider extracting a shared
<JsonLd data={...} />(ornext/scriptwrapper) where you locally disable the lint, and reuse that across tool pages; or document the exception and suppress the rule just on this line.docs/src/app/(home)/tools/meta-description-generator/page.tsx (1)
38-115: Repeat JSON-LD pattern: static data is fine; centralize or lint-suppressSame pattern as other tool pages: static
structuredDataserialized into a JSON-LD<script>viadangerouslySetInnerHTML. This is effectively safe given the lack of user input, but it will keep tripping security linters.I’d suggest either:
- Reusing a shared
<JsonLd>helper that encapsulates thedangerouslySetInnerHTMLusage (and any lint suppression), or- Adding a targeted lint disable around this line and documenting that only static objects may be passed.
Also ensure the canonical URL (
https://rybbit.com/tools/meta-description-generator) matches whatever domain you decide on for all tools, as noted on the privacy-policy-builder review.docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx (1)
46-127: Static JSON-LD script again safe, but centralization would reduce lint noise
structuredDatais a compile-time object, and the JSON-LD<script>usesJSON.stringify, so there’s no real XSS surface here. Nevertheless, Biome and ast-grep will keep flaggingdangerouslySetInnerHTML.Same suggestion as other files: centralize this into a reusable helper (or
next/scriptwrapper) with a documented lint override, or apply a local lint suppression and clearly state that only static data should be passed.docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (1)
41-113: JSON-LD schema inside component: safe, but consider hoisting/centralizing
schemaDatais static and only used to render a JSON-LD<script>viadangerouslySetInnerHTML. That’s safe from XSS here, but:
- You can hoist
schemaDatato module scope to avoid re-allocating it per render.- As with other pages, you may want a shared
<JsonLd data={schemaData} />(or equivalent) to encapsulate thedangerouslySetInnerHTMLusage and handle lint suppression in one place.This would reduce duplication and keep security tooling happy.
docs/src/app/(home)/tools/og-tag-generator/page.tsx (2)
68-110: Duplicated FAQ content between JSON‑LD and visible AccordionThe FAQ Q&A content is duplicated in both
structuredData(FAQPage) and the Accordion UI. This is fine functionally, but it does create a maintenance hotspot: any future copy edits will need to be applied in two places and it’s easy for them to drift.If you expect to iterate on this content, consider centralizing the FAQ data in a shared array and:
- Map it into
structuredData.@graph[1].mainEntity, and- Map it again into the Accordion items.
That keeps JSON‑LD and visible content in sync with a single source of truth.
Also applies to: 395-461
333-345: Consider pointing to LinkedIn’s official “Post Inspector” toolThe copy refers to LinkedIn’s Post Inspector but the example URLs and description lean toward general feed usage. To avoid confusing users, you might want to either:
- Link directly to LinkedIn’s official Post Inspector endpoint, or
- Rephrase to clearly distinguish “test via the feed preview” vs using a dedicated inspector tool.
This is content‑only, but adjusting it would make the testing guidance clearer.
Also applies to: 424-437
docs/src/app/(home)/tools/seo-title-generator/page.tsx (1)
62-104: Shared FAQ content for schema and UI could be data‑drivenThe same FAQ questions/answers are encoded twice: once in
structuredData(FAQPage) and once in the Accordion. That’s OK now, but it’s easy for them to diverge over time.If you anticipate copy changes, consider extracting a shared data structure like:
const faqItems = [ { id: "item-1", question: "What makes a good SEO title?", answer: "A good SEO title is 50-60 characters, includes your primary keyword, accurately describes the page content, and is compelling enough to attract clicks. Front-load important keywords and make it unique for each page.", }, // ... ];Then:
- Map
faqItemsintostructuredData.@graph[1].mainEntity, and- Map
faqItemsinto the Accordion JSX.That keeps JSON‑LD and visible FAQs in sync with one source of truth.
Also applies to: 226-290
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (33)
docs/src/app/(home)/oss-friends/page.tsx(1 hunks)docs/src/app/(home)/tools/ai-privacy-policy-generator/PrivacyPolicyForm.tsx(1 hunks)docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx(1 hunks)docs/src/app/(home)/tools/analytics-detector/AnalyticsDetectorForm.tsx(1 hunks)docs/src/app/(home)/tools/analytics-detector/page.tsx(4 hunks)docs/src/app/(home)/tools/bounce-rate-calculator/BounceRateForm.tsx(1 hunks)docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/ctr-calculator/CTRCalculatorForm.tsx(1 hunks)docs/src/app/(home)/tools/ctr-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/funnel-visualizer/FunnelVisualizerForm.tsx(1 hunks)docs/src/app/(home)/tools/funnel-visualizer/page.tsx(1 hunks)docs/src/app/(home)/tools/marketing-roi-calculator/MarketingROIForm.tsx(1 hunks)docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/meta-description-generator/MetaDescriptionForm.tsx(1 hunks)docs/src/app/(home)/tools/meta-description-generator/page.tsx(1 hunks)docs/src/app/(home)/tools/og-tag-generator/OGTagForm.tsx(1 hunks)docs/src/app/(home)/tools/og-tag-generator/page.tsx(1 hunks)docs/src/app/(home)/tools/page-speed-calculator/PageSpeedForm.tsx(1 hunks)docs/src/app/(home)/tools/page-speed-calculator/page.tsx(2 hunks)docs/src/app/(home)/tools/page.tsx(1 hunks)docs/src/app/(home)/tools/privacy-policy-builder/PrivacyPolicyBuilderForm.tsx(1 hunks)docs/src/app/(home)/tools/privacy-policy-builder/page.tsx(1 hunks)docs/src/app/(home)/tools/sample-size-calculator/SampleSizeForm.tsx(1 hunks)docs/src/app/(home)/tools/sample-size-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/seo-title-generator/SEOTitleForm.tsx(1 hunks)docs/src/app/(home)/tools/seo-title-generator/page.tsx(1 hunks)docs/src/app/(home)/tools/traffic-value-calculator/TrafficValueForm.tsx(1 hunks)docs/src/app/(home)/tools/traffic-value-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/utm-builder/UTMBuilderForm.tsx(1 hunks)docs/src/app/(home)/tools/utm-builder/page.tsx(1 hunks)docs/src/app/sitemap.ts(3 hunks)docs/src/components/Breadcrumbs.tsx(1 hunks)docs/src/components/RelatedTools.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (16)
docs/src/app/(home)/tools/traffic-value-calculator/TrafficValueForm.tsx (1)
docs/src/components/ui/accordion.tsx (4)
Accordion(58-58)AccordionItem(58-58)AccordionTrigger(58-58)AccordionContent(58-58)
docs/src/app/(home)/tools/seo-title-generator/SEOTitleForm.tsx (1)
docs/src/components/TrackedButton.tsx (1)
TrackedButton(16-31)
docs/src/app/(home)/tools/bounce-rate-calculator/BounceRateForm.tsx (1)
docs/src/components/ui/accordion.tsx (4)
Accordion(58-58)AccordionItem(58-58)AccordionTrigger(58-58)AccordionContent(58-58)
docs/src/app/(home)/tools/privacy-policy-builder/PrivacyPolicyBuilderForm.tsx (1)
docs/src/components/TrackedButton.tsx (1)
TrackedButton(16-31)
docs/src/app/(home)/tools/funnel-visualizer/FunnelVisualizerForm.tsx (2)
client/src/api/analytics/funnels/useGetFunnel.ts (1)
FunnelStep(8-15)client/src/app/[site]/funnels/components/Funnel.tsx (1)
FunnelChartData(13-19)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (2)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/funnel-visualizer/FunnelVisualizerForm.tsx (1)
FunnelVisualizerForm(20-230)
docs/src/app/(home)/tools/privacy-policy-builder/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/privacy-policy-builder/PrivacyPolicyBuilderForm.tsx (1)
PrivacyPolicyBuilderForm(7-335)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-160)
docs/src/app/(home)/tools/meta-description-generator/MetaDescriptionForm.tsx (1)
docs/src/components/TrackedButton.tsx (1)
TrackedButton(16-31)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (4)
docs/src/app/(home)/tools/page.tsx (1)
metadata(18-22)docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-160)docs/src/components/TrackedButton.tsx (1)
TrackedButton(16-31)
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/bounce-rate-calculator/BounceRateForm.tsx (1)
BounceRateForm(17-233)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-160)
docs/src/app/(home)/tools/analytics-detector/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/analytics-detector/AnalyticsDetectorForm.tsx (1)
AnalyticsDetectorForm(19-182)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-160)
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/ai-privacy-policy-generator/PrivacyPolicyForm.tsx (1)
PrivacyPolicyForm(6-198)docs/src/components/TrackedButton.tsx (1)
TrackedButton(16-31)
docs/src/app/(home)/tools/page-speed-calculator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/page-speed-calculator/PageSpeedForm.tsx (1)
PageSpeedForm(22-365)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-160)
docs/src/app/(home)/tools/utm-builder/UTMBuilderForm.tsx (1)
docs/src/components/ui/accordion.tsx (4)
Accordion(58-58)AccordionItem(58-58)AccordionTrigger(58-58)AccordionContent(58-58)
docs/src/app/(home)/tools/ctr-calculator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/ctr-calculator/CTRCalculatorForm.tsx (1)
CTRCalculatorForm(15-158)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-160)
docs/src/app/(home)/tools/meta-description-generator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/meta-description-generator/MetaDescriptionForm.tsx (1)
MetaDescriptionForm(14-198)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-160)
🪛 ast-grep (0.40.0)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx
[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/utm-builder/page.tsx
[warning] 113-113: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/privacy-policy-builder/page.tsx
[warning] 114-114: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx
[warning] 112-112: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/og-tag-generator/page.tsx
[warning] 117-117: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx
[warning] 112-112: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx
[warning] 126-126: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/analytics-detector/page.tsx
[warning] 114-114: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx
[warning] 126-126: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/page-speed-calculator/page.tsx
[warning] 118-118: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/ctr-calculator/page.tsx
[warning] 117-117: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/seo-title-generator/page.tsx
[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/meta-description-generator/page.tsx
[warning] 113-113: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx
[warning] 115-115: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx
[error] 117-117: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/utm-builder/page.tsx
[error] 114-114: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/privacy-policy-builder/page.tsx
[error] 115-115: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx
[error] 113-113: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/og-tag-generator/page.tsx
[error] 118-118: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx
[error] 113-113: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx
[error] 127-127: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/analytics-detector/page.tsx
[error] 115-115: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx
[error] 127-127: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/page-speed-calculator/page.tsx
[error] 119-119: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/ctr-calculator/page.tsx
[error] 118-118: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/seo-title-generator/page.tsx
[error] 112-112: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/meta-description-generator/page.tsx
[error] 114-114: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx
[error] 116-116: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Client Images (ubuntu-24.04-arm, linux/arm64)
- GitHub Check: Build Client Images (ubuntu-latest, linux/amd64)
🔇 Additional comments (32)
docs/src/app/(home)/oss-friends/page.tsx (1)
42-42: Verify styling consistency with broader refactoring pattern.The background gradient classes have been removed from the page container. While this is a minor styling change and doesn't affect functionality, consider whether this aligns with the broader design direction across the site.
Additionally, based on the PR's focus on enhancing SEO and discoverability, this page may benefit from the same treatment as other refactored tool pages (Breadcrumbs, RelatedTools, and JSON-LD structured data), though it's unclear if this page is intended to be part of that scope.
docs/src/app/sitemap.ts (3)
69-74: Good priority hierarchy for the tools index page.The tools index page at priority 0.85 appropriately ranks above individual tool pages at 0.8, which helps search engines understand the site structure.
Note: The
weeklychange frequency assumes tools are added/updated weekly. If updates are less frequent, considermonthlyto match the individual tool pages.
83-83: LGTM! Clean sitemap structure.The return order logically groups static pages, tool pages, documentation, and blog posts. This maintains good organization for the sitemap.
1-1: No action required.Next.js 16.0.0 was released on October 21, 2025, and the code correctly uses this valid version. The original review comment's concern about a version discrepancy is incorrect. While the latest patch is 16.0.3, the use of 16.0.0 is not problematic.
Likely an incorrect or invalid review comment.
docs/src/components/Breadcrumbs.tsx (1)
1-48: LGTM! Well-structured breadcrumb component.The component properly implements WCAG breadcrumb navigation with appropriate ARIA attributes, conditional link rendering, and visual separators. Using index as key is acceptable here since breadcrumb items are static and won't be reordered.
docs/src/app/(home)/tools/ctr-calculator/CTRCalculatorForm.tsx (1)
1-158: LGTM! Clean CTR calculator implementation.The component correctly calculates CTR, handles edge cases (division by zero), and provides meaningful benchmark comparisons. The logic is sound and the UI feedback is contextual.
docs/src/app/(home)/tools/page.tsx (1)
1-177: LGTM! Clean tools listing page.The page properly exports metadata for SEO, uses Next.js Link for optimal client-side navigation, and provides a well-structured grid layout for tool discovery. The implementation is straightforward and follows Next.js best practices.
docs/src/app/(home)/tools/sample-size-calculator/SampleSizeForm.tsx (1)
11-33: LGTM! Statistically sound sample size calculation.The implementation correctly applies the two-proportion sample size formula with appropriate Z-scores for the selected confidence levels and statistical power. Input validation and edge case handling are properly implemented.
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (1)
45-48: JSON-LD injection viadangerouslySetInnerHTMLis acceptable here but deserves a commentStatic analysis flags
dangerouslySetInnerHTML, but in this caseschemaDatais a static object and is serialized withJSON.stringify, with no user input. That makes this a standard, low-risk pattern for JSON-LD.To make this intent clear (and keep linters quiet), consider adding a brief comment and/or a localized rule suppression:
- <> - <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} /> + <> + {/* Safe JSON-LD injection: schemaData is static and only serialized via JSON.stringify */} + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} + />If your tooling supports it, you can also add an inline disable for this specific line instead of relaxing the rule globally.
Also applies to: 117-117
docs/src/app/(home)/tools/ctr-calculator/page.tsx (1)
120-491: Page composition, content structure, and CTA wiring look solidBreadcrumbs,
CTRCalculatorForm, educational sections, FAQ accordion,RelatedTools, and the CTA are composed cleanly and are consistent with the other tools. Nothing blocking from a correctness or UX perspective here.docs/src/app/(home)/tools/utm-builder/UTMBuilderForm.tsx (1)
8-231: UTM URL construction and form UX look goodThe memoized UTM URL builder, required vs optional fields, conditional result section, and copy‑with‑feedback flow are all straightforward and correct for common UTM use cases.
docs/src/app/(home)/tools/seo-title-generator/SEOTitleForm.tsx (1)
15-199: Fetch flow, rate‑limit handling, and result presentation look solidThe POST flow to
/api/tools/generate-seo-title, header-based rate‑limit display, per‑item copy state, and length coloring logic are all consistent and correct. No blocking issues from a logic standpoint.docs/src/app/(home)/tools/sample-size-calculator/page.tsx (1)
129-547: Overall page structure and educational content look strongBreadcrumbs,
SampleSizeFormintegration, well‑layered educational sections, FAQ accordion,RelatedTools, and the CTA are all wired correctly and read well; no functional concerns here.docs/src/app/(home)/tools/analytics-detector/AnalyticsDetectorForm.tsx (1)
60-180: Detection flow, result rendering, and rate‑limit display look goodThe POST flow to
/api/tools/detect-analytics, handling ofX-RateLimit-Remaining, summary + privacy score card, and platform list rendering are all straightforward and functionally sound.docs/src/app/(home)/tools/analytics-detector/page.tsx (1)
118-355: Page composition and content restructuring look solidBreadcrumbs,
AnalyticsDetectorForm, expanded educational sections, updated FAQ, andRelatedToolsintegration are wired correctly and give a consistent UX with other tools. No functional issues spotted in this section.docs/src/app/(home)/tools/utm-builder/page.tsx (2)
8-41: Metadata configuration is consistent and SEO‑friendlyTitle/description/keywords plus OpenGraph, Twitter, and canonical URL for
https://docs.rybbit.io/tools/utm-builderare coherent and match the route structure. No issues here.
116-427: Form embedding and educational content are cleanly structured
UTMBuilderFormintegration, Breadcrumbs/RelatedTools usage, and the surrounding explanatory sections (parameters, naming conventions, use cases, mistakes, how‑to, CTA) are well organized and consistent with other tools. I don’t see functional issues in this block.docs/src/app/(home)/tools/og-tag-generator/OGTagForm.tsx (2)
26-58:generateOGTagsflow and error handling look goodInput validation, loading state management, rate‑limit header parsing, and API error surface (
data.errorfallback) are all reasonable. No blocking issues here.
66-205: Form UX and results rendering are solidField wiring, disabled states during loading, per‑variation copy feedback, and the summary layout for generated OG tag variations are all clear and consistent. No further issues from my side.
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx (2)
8-43: Traffic value page metadata is coherentMetadata (title, description, keywords, OpenGraph/Twitter, canonical) aligns with the route and page content; nothing problematic here.
118-403: Content sections andTrafficValueFormintegration look correctBreadcrumbs, the form block, explanatory sections (what traffic value is, formula, factors, monetization strategies, benchmarks), RelatedTools, and CTA are all wired as expected. I don’t see functional or structural issues in this portion.
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (2)
8-40: Bounce rate metadata setup is consistent and appropriateMetadata for title/description, keyword set, OpenGraph/Twitter, and canonical URL all match the route and content; no changes needed.
115-377: Form integration and descriptive sections are well implemented
BounceRateFormusage, Breadcrumbs, the explanatory sections (what bounce rate is, why it matters, formula, benchmarks, how to reduce it), RelatedTools, and the CTA all look consistent and functionally sound. No issues to flag here.docs/src/app/(home)/tools/page-speed-calculator/page.tsx (2)
6-45: Metadata configuration looks correct and aligned with the tool URLTitle, description, keywords, Open Graph, Twitter, and canonical fields are coherent and point to
https://rybbit.io/tools/page-speed-calculator. No issues from a Next.jsMetadataperspective.
118-209: Page composition with Breadcrumbs, form, and related tools is cleanThe structure (
Breadcrumbs→ hero copy → educational sections →PageSpeedForm→RelatedTools) is clear and consistent with other tools; nothing blocking from a correctness or UX standpoint.docs/src/app/(home)/tools/privacy-policy-builder/page.tsx (2)
117-323: Overall page structure and disclaimers look solidBreadcrumbs, educational sections,
PrivacyPolicyBuilderForm, legal disclaimer, FAQ, andRelatedToolsare wired together coherently. Nothing stands out as a functional or UX issue in this layout.
8-38: Incorrect characterization of domain usage across tool pagesThe review claims "other tool pages use
rybbit.ioordocs.rybbit.io," but verification shows tool pages actually use three different domains:rybbit.com(6 tools),docs.rybbit.io(5 tools), andrybbit.io(3 tools). The privacy-policy-builder page usingrybbit.comis not an outlier—six other tool pages also userybbit.com, including analytics-detector, meta-description-generator, og-tag-generator, sample-size-calculator, and seo-title-generator.While there is a legitimate domain inconsistency issue across tools that warrants addressing, the review's characterization of which domains are in use is inaccurate.
Likely an incorrect or invalid review comment.
docs/src/app/(home)/tools/meta-description-generator/page.tsx (1)
111-304: Form integration and FAQ/education content are wired correctly
MetaDescriptionFormis composed cleanly with the hero, best-practices sections, FAQ, andRelatedTools. No obvious logic, accessibility, or routing issues here.docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx (1)
124-533: Rich legal/UX content, form, FAQ, and CTA are composed correctlyThe
PrivacyPolicyForm, disclaimers, compliance sections, FAQ,RelatedTools, and CTA withTrackedButton/DEFAULT_EVENT_LIMITare structured cleanly and appear consistent with the rest of the tooling pages. No functional issues spotted.docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (1)
111-505: Form, educational content, FAQ, RelatedTools, and CTA wiring look correct
MarketingROIFormis integrated cleanly with the hero, multi-section educational content, FAQ (aligned with the JSON-LD FAQ entries),RelatedTools, and the finalTrackedButtonCTA. No correctness or UX concerns from this composition.docs/src/app/(home)/tools/og-tag-generator/page.tsx (1)
10-41: Verify metadata URLs and alignment with deployed routeThe
openGraph.urlandalternates.canonicalvalues are hard‑coded tohttps://rybbit.com/tools/og-tag-generator. Please double‑check that this domain and path exactly match the deployed URL for this page so you don’t end up with mismatched canonical/OG URLs if routing or domains change.docs/src/app/(home)/tools/seo-title-generator/page.tsx (1)
8-37: Confirm metadata URLs and path consistencyAs with the OG page,
openGraph.urlandalternates.canonicalare hard‑coded tohttps://rybbit.com/tools/seo-title-generator. Please confirm this exactly matches the live route (including domain and any subpaths) so canonical and OG signals remain correct if routing changes.
| const detectAnalytics = async () => { | ||
| if (!url) { | ||
| setError("Please enter a website URL"); | ||
| return; | ||
| } | ||
|
|
||
| setIsLoading(true); | ||
| setError(""); | ||
| setResult(null); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear stale results on validation error and guard against double submits
If the user has a previous detection result and then clicks “Detect Analytics” with an empty URL, you set an error but keep showing the old result. Also, pressing Enter in the input while a request is in flight can fire another detectAnalytics call.
Consider:
const detectAnalytics = async () => {
- if (!url) {
- setError("Please enter a website URL");
- return;
- }
+ if (!url) {
+ setError("Please enter a website URL");
+ setResult(null);
+ return;
+ }
+
+ if (isLoading) return;This avoids stale results when validation fails and prevents accidental double submissions via the Enter key.
Also applies to: 80-86
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/analytics-detector/AnalyticsDetectorForm.tsx around
lines 26-35 and 80-86, validation currently sets an error but leaves any
previous result visible and nothing prevents re-submitting while a request is in
flight. Fix by clearing setResult(null) whenever validation fails (e.g., when
url is empty) so stale results are not shown, and add a guard at the start of
detectAnalytics to return early if isLoading is true to prevent double
submissions (also ensure any Enter-key handler checks isLoading before calling
detectAnalytics).
| const structuredData = { | ||
| "@context": "https://schema.org", | ||
| "@graph": [ | ||
| { | ||
| "@type": "WebApplication", | ||
| name: "Open Graph Tag Generator", | ||
| applicationCategory: "SEO Tool", | ||
| description: "Free tool to generate optimized Open Graph tags for social sharing", | ||
| offers: { | ||
| "@type": "Offer", | ||
| price: "0", | ||
| priceCurrency: "USD", | ||
| }, | ||
| featureList: [ | ||
| "AI-powered OG tag generation", | ||
| "Multiple tag variations", | ||
| "Twitter Card optimization", | ||
| "Social preview preview", | ||
| "HTML code generation", | ||
| "Image size recommendations", | ||
| ], | ||
| operatingSystem: "Any", | ||
| url: "https://rybbit.com/tools/og-tag-generator", | ||
| }, | ||
| { | ||
| "@type": "FAQPage", | ||
| mainEntity: [ | ||
| { | ||
| "@type": "Question", | ||
| name: "What are Open Graph tags and why do I need them?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "Open Graph (OG) tags are meta tags in your HTML head that control how your content appears when shared on social media platforms like Facebook, LinkedIn, and Twitter. They define the title, description, image, and type of content that appears in the social preview. Without OG tags, social media platforms use their default parsing which often results in poor-looking previews. Optimized OG tags significantly increase click-through rates and engagement when content is shared.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "What are the required vs optional Open Graph tags?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "Required OG tags are: og:title, og:type, og:image, and og:url. These four tags provide the minimum information needed for a social preview. Optional but recommended tags include og:description (summary of content), og:locale (language), and og:site_name. For articles, add og:article:published_time and og:article:author. Twitter Cards require card type, title, and description at minimum.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "What are the correct image sizes and specifications for OG tags?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "For og:image, use images that are 1200x630 pixels (1.91:1 aspect ratio) for optimal display on most platforms. Minimum recommended size is 200x200 pixels. File size should be under 8MB. Twitter Card images for summary_large_image should be 506x506 minimum, preferably 1024x512. Use JPG or PNG formats. For og:image:width and og:image:height, specify the actual dimensions of your image. Square images work best for profiles and smaller feeds.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "How do I test my Open Graph tags?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "Use Facebook's Sharing Debugger (facebook.com/developers/tools/debug/) to see how your pages appear on Facebook. Use LinkedIn's Post Inspector (linkedin.com/feed/update/urn:li:activity) to test LinkedIn previews. For Twitter, use the Card Validator (cards-dev.twitter.com/validator). Always clear the cache after making changes. Test on actual social platforms by pasting your URL in the status update box. Track social referral traffic with Rybbit Analytics to see which OG tags drive the most clicks.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "What's the difference between Open Graph tags and Twitter Cards?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "Open Graph tags are a protocol developed by Facebook that works across multiple platforms. Twitter Cards use the twitter: prefix and allow more granular control over Twitter-specific previews. Twitter Cards types include summary (small card with summary), summary_large_image (large image card), app (mobile app card), and player (video player card). You should implement both OG tags and Twitter Card tags for complete social optimization. Twitter will fall back to OG tags if Twitter Card tags are missing.", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| export default function OGTagGeneratorPage() { | ||
| return ( | ||
| <div className="min-h-screen bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-950 dark:to-neutral-900"> | ||
| <div className="max-w-4xl mx-auto px-6 py-20"> | ||
| {/* Header */} | ||
| <div className="mb-16"> | ||
| <div className="inline-block mb-4 px-4 py-1.5 bg-emerald-100 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800 rounded-full"> | ||
| <span className="text-sm font-medium text-emerald-700 dark:text-emerald-400">AI-Powered Tool</span> | ||
| </div> | ||
| <h1 className="text-5xl md:text-6xl font-bold text-neutral-900 dark:text-white mb-6 tracking-tight"> | ||
| Open Graph Tag Generator | ||
| </h1> | ||
| <p className="text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl leading-relaxed"> | ||
| Generate optimized Open Graph tags for social media sharing. Get perfect previews on Facebook, Twitter, and LinkedIn. | ||
| </p> | ||
| {remainingRequests !== null && ( | ||
| <p className="text-sm text-neutral-500 dark:text-neutral-400 mt-4"> | ||
| {remainingRequests} requests remaining this minute | ||
| </p> | ||
| )} | ||
| </div> | ||
|
|
||
| {/* Tool */} | ||
| <div className="mb-16"> | ||
| <div className="space-y-6"> | ||
| <div> | ||
| <label className="block text-sm font-medium text-neutral-900 dark:text-white mb-2"> | ||
| Page Title <span className="text-red-500">*</span> | ||
| </label> | ||
| <input | ||
| type="text" | ||
| value={pageTitle} | ||
| onChange={e => setPageTitle(e.target.value)} | ||
| placeholder="e.g., The Ultimate Guide to Web Analytics" | ||
| disabled={isLoading} | ||
| className="w-full px-4 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-emerald-500 disabled:opacity-50" | ||
| /> | ||
| <> | ||
| <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
dangerouslySetInnerHTML for JSON‑LD: safe here but breaking linters; add explicit ignore
structuredData is a static object defined in this file, so injecting it via dangerouslySetInnerHTML is effectively safe from XSS. However, ast‑grep and Biome are flagging this as a security error, which can break CI.
Consider explicitly documenting and suppressing the lint on this line so future reviewers see that it’s an intentional, safe JSON‑LD pattern. For example:
- <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */}
+ <script
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
+ />If you also have a custom ast‑grep rule, you may want to scope it to only flag cases where the source is not a static object literal.
I'll verify this review comment by checking the file, linter configurations, and whether the suggested fix approach is correct.
---
🏁 Script executed:
```shell
#!/bin/bash
# Check if the file exists and examine the code around lines 43-118
if [ -f "docs/src/app/(home)/tools/og-tag-generator/page.tsx" ]; then
echo "=== FILE EXISTS ==="
sed -n '40,120p' "docs/src/app/(home)/tools/og-tag-generator/page.tsx"
else
echo "File not found at expected path"
# Try to find it
find . -name "*og-tag-generator*" -type f 2>/dev/null | head -10
fi
Length of output: 4504
🏁 Script executed:
#!/bin/bash
# Look for Biome configuration files
echo "=== SEARCHING FOR BIOME CONFIG ==="
find . -name "biome.json" -o -name "biome.jsonc" -o -name ".biomeignore" 2>/dev/null | head -10
# Check if biome.json exists and show its content
if [ -f "biome.json" ]; then
echo "=== BIOME.JSON CONTENT ==="
cat biome.json
fiLength of output: 1849
🏁 Script executed:
#!/bin/bash
# Look for ast-grep configuration
echo "=== SEARCHING FOR AST-GREP CONFIG ==="
find . -name "astgrep.yaml" -o -name ".astgrep.yaml" -o -name "ast-grep.yaml" 2>/dev/null | head -10
if [ -f "astgrep.yaml" ]; then
echo "=== ASTGREP.YAML CONTENT ==="
cat astgrep.yaml
fi
if [ -f ".astgrep.yaml" ]; then
echo "=== .ASTGREP.YAML CONTENT ==="
cat .astgrep.yaml
fiLength of output: 37
🏁 Script executed:
#!/bin/bash
# Search for any dangerouslySetInnerHTML usage in the codebase to see patterns
echo "=== DANGEROUSLYSETINNERHTML USAGE PATTERNS ==="
rg -n "dangerouslySetInnerHTML" -A2 -B2 --type=tsx --type=ts 2>/dev/null | head -40Length of output: 46
Let me verify whether Biome actually has this security rule and if it would flag this pattern.
---
🌐 Web query:
```web
Biome noDangerouslySetInnerHtml security rule React
💡 Result:
Summary: Biome includes a security lint rule lint/security/noDangerouslySetInnerHtml that forbids using React's dangerouslySetInnerHTML (to prevent XSS). It's recommended/enabled by default, has default severity "error", and has no automatic fixer. Configure via biome.json under linter.rules.security. [1][2][3]
Quick config example (biome.json):
{
"linter": {
"rules": {
"security": {
"noDangerouslySetInnerHtml": "error"
}
}
}
}
Related: there's also noDangerouslySetInnerHtmlWithChildren which flags using children together with dangerouslySetInnerHTML. [2] Sources: Biome docs. [1][2][3]
Add biome-ignore comment to suppress security rule for static JSON-LD
The review is accurate. Biome's lint/security/noDangerouslySetInnerHtml rule is enabled by default, and it will flag the dangerouslySetInnerHTML usage even though structuredData is a static object with no XSS risk. Adding the biome-ignore comment is the correct pattern to suppress this false positive:
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */}
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />This prevents CI breakage while documenting why the security rule is intentionally disabled at this location.
📝 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.
| const structuredData = { | |
| "@context": "https://schema.org", | |
| "@graph": [ | |
| { | |
| "@type": "WebApplication", | |
| name: "Open Graph Tag Generator", | |
| applicationCategory: "SEO Tool", | |
| description: "Free tool to generate optimized Open Graph tags for social sharing", | |
| offers: { | |
| "@type": "Offer", | |
| price: "0", | |
| priceCurrency: "USD", | |
| }, | |
| featureList: [ | |
| "AI-powered OG tag generation", | |
| "Multiple tag variations", | |
| "Twitter Card optimization", | |
| "Social preview preview", | |
| "HTML code generation", | |
| "Image size recommendations", | |
| ], | |
| operatingSystem: "Any", | |
| url: "https://rybbit.com/tools/og-tag-generator", | |
| }, | |
| { | |
| "@type": "FAQPage", | |
| mainEntity: [ | |
| { | |
| "@type": "Question", | |
| name: "What are Open Graph tags and why do I need them?", | |
| acceptedAnswer: { | |
| "@type": "Answer", | |
| text: "Open Graph (OG) tags are meta tags in your HTML head that control how your content appears when shared on social media platforms like Facebook, LinkedIn, and Twitter. They define the title, description, image, and type of content that appears in the social preview. Without OG tags, social media platforms use their default parsing which often results in poor-looking previews. Optimized OG tags significantly increase click-through rates and engagement when content is shared.", | |
| }, | |
| }, | |
| { | |
| "@type": "Question", | |
| name: "What are the required vs optional Open Graph tags?", | |
| acceptedAnswer: { | |
| "@type": "Answer", | |
| text: "Required OG tags are: og:title, og:type, og:image, and og:url. These four tags provide the minimum information needed for a social preview. Optional but recommended tags include og:description (summary of content), og:locale (language), and og:site_name. For articles, add og:article:published_time and og:article:author. Twitter Cards require card type, title, and description at minimum.", | |
| }, | |
| }, | |
| { | |
| "@type": "Question", | |
| name: "What are the correct image sizes and specifications for OG tags?", | |
| acceptedAnswer: { | |
| "@type": "Answer", | |
| text: "For og:image, use images that are 1200x630 pixels (1.91:1 aspect ratio) for optimal display on most platforms. Minimum recommended size is 200x200 pixels. File size should be under 8MB. Twitter Card images for summary_large_image should be 506x506 minimum, preferably 1024x512. Use JPG or PNG formats. For og:image:width and og:image:height, specify the actual dimensions of your image. Square images work best for profiles and smaller feeds.", | |
| }, | |
| }, | |
| { | |
| "@type": "Question", | |
| name: "How do I test my Open Graph tags?", | |
| acceptedAnswer: { | |
| "@type": "Answer", | |
| text: "Use Facebook's Sharing Debugger (facebook.com/developers/tools/debug/) to see how your pages appear on Facebook. Use LinkedIn's Post Inspector (linkedin.com/feed/update/urn:li:activity) to test LinkedIn previews. For Twitter, use the Card Validator (cards-dev.twitter.com/validator). Always clear the cache after making changes. Test on actual social platforms by pasting your URL in the status update box. Track social referral traffic with Rybbit Analytics to see which OG tags drive the most clicks.", | |
| }, | |
| }, | |
| { | |
| "@type": "Question", | |
| name: "What's the difference between Open Graph tags and Twitter Cards?", | |
| acceptedAnswer: { | |
| "@type": "Answer", | |
| text: "Open Graph tags are a protocol developed by Facebook that works across multiple platforms. Twitter Cards use the twitter: prefix and allow more granular control over Twitter-specific previews. Twitter Cards types include summary (small card with summary), summary_large_image (large image card), app (mobile app card), and player (video player card). You should implement both OG tags and Twitter Card tags for complete social optimization. Twitter will fall back to OG tags if Twitter Card tags are missing.", | |
| }, | |
| }, | |
| ], | |
| }, | |
| ], | |
| }; | |
| export default function OGTagGeneratorPage() { | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-950 dark:to-neutral-900"> | |
| <div className="max-w-4xl mx-auto px-6 py-20"> | |
| {/* Header */} | |
| <div className="mb-16"> | |
| <div className="inline-block mb-4 px-4 py-1.5 bg-emerald-100 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800 rounded-full"> | |
| <span className="text-sm font-medium text-emerald-700 dark:text-emerald-400">AI-Powered Tool</span> | |
| </div> | |
| <h1 className="text-5xl md:text-6xl font-bold text-neutral-900 dark:text-white mb-6 tracking-tight"> | |
| Open Graph Tag Generator | |
| </h1> | |
| <p className="text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl leading-relaxed"> | |
| Generate optimized Open Graph tags for social media sharing. Get perfect previews on Facebook, Twitter, and LinkedIn. | |
| </p> | |
| {remainingRequests !== null && ( | |
| <p className="text-sm text-neutral-500 dark:text-neutral-400 mt-4"> | |
| {remainingRequests} requests remaining this minute | |
| </p> | |
| )} | |
| </div> | |
| {/* Tool */} | |
| <div className="mb-16"> | |
| <div className="space-y-6"> | |
| <div> | |
| <label className="block text-sm font-medium text-neutral-900 dark:text-white mb-2"> | |
| Page Title <span className="text-red-500">*</span> | |
| </label> | |
| <input | |
| type="text" | |
| value={pageTitle} | |
| onChange={e => setPageTitle(e.target.value)} | |
| placeholder="e.g., The Ultimate Guide to Web Analytics" | |
| disabled={isLoading} | |
| className="w-full px-4 py-3 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-emerald-500 disabled:opacity-50" | |
| /> | |
| <> | |
| <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> | |
| export default function OGTagGeneratorPage() { | |
| return ( | |
| <> | |
| {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */} | |
| <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> |
🧰 Tools
🪛 ast-grep (0.40.0)
[warning] 117-117: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
[error] 118-118: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/og-tag-generator/page.tsx around lines 43-118, the
use of dangerouslySetInnerHTML for static JSON-LD is flagged by Biome's
lint/security/noDangerouslySetInnerHtml rule; add a biome-ignore comment
immediately above the script tag to suppress that specific rule for this
known-safe static object, mentioning the rule name
(lint/security/noDangerouslySetInnerHtml) and a short note that the JSON-LD is
static to document the intentional suppression.
| // Bounce rate calculation (assume current is 50% baseline) | ||
| const currentBounceRate = 50; | ||
| const newBounceRate = timeDifference < 0 | ||
| ? Math.max(currentBounceRate + bounceRateIncrease, 0) | ||
| : Math.min(currentBounceRate + bounceRateIncrease, 100); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix bounce rate calculation logic.
The bounce rate calculation has inverted logic. When timeDifference > 0 (speed improves), the bounce rate should decrease, not increase. Currently, both branches add bounceRateIncrease, which means improving speed incorrectly increases the bounce rate.
Apply this diff to fix the logic:
// Bounce rate calculation (assume current is 50% baseline)
const currentBounceRate = 50;
- const newBounceRate = timeDifference < 0
- ? Math.max(currentBounceRate + bounceRateIncrease, 0)
- : Math.min(currentBounceRate + bounceRateIncrease, 100);
+ const newBounceRate = timeDifference > 0
+ ? Math.max(currentBounceRate - bounceRateIncrease, 0) // Improving: reduce bounce rate
+ : Math.min(currentBounceRate + bounceRateIncrease, 100); // Degrading: increase bounce rate📝 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.
| // Bounce rate calculation (assume current is 50% baseline) | |
| const currentBounceRate = 50; | |
| const newBounceRate = timeDifference < 0 | |
| ? Math.max(currentBounceRate + bounceRateIncrease, 0) | |
| : Math.min(currentBounceRate + bounceRateIncrease, 100); | |
| // Bounce rate calculation (assume current is 50% baseline) | |
| const currentBounceRate = 50; | |
| const newBounceRate = timeDifference > 0 | |
| ? Math.max(currentBounceRate - bounceRateIncrease, 0) // Improving: reduce bounce rate | |
| : Math.min(currentBounceRate + bounceRateIncrease, 100); // Degrading: increase bounce rate |
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/page-speed-calculator/PageSpeedForm.tsx around
lines 56 to 61, the bounce rate logic is inverted: both branches add
bounceRateIncrease so improvements (timeDifference > 0) incorrectly raise bounce
rate. Fix by using subtraction on the improvement branch: when timeDifference >
0 compute Math.max(currentBounceRate - bounceRateIncrease, 0), otherwise (speed
worsens or timeDifference < 0) compute Math.min(currentBounceRate +
bounceRateIncrease, 100); keep currentBounceRate constant and clamp results to
[0,100].
| const structuredData = { | ||
| "@context": "https://schema.org", | ||
| "@graph": [ | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
JSON‑LD injection: static and safe, but align with lint rules
You’re using the standard JSON‑LD pattern:
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />Given structuredData is a static, local object, this is safe from user‑controlled XSS. To keep CI happy with Biome and ast‑grep:
- <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is static JSON-LD defined in this file */}
+ <script
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
+ />And, if needed, narrow your custom static‑analysis rule so JSON‑LD cases like this are exempt.
Also applies to: 111-113
---
🏁 Script executed:
```shell
#!/bin/bash
# Examine the file at the specified lines
cat "docs/src/app/(home)/tools/seo-title-generator/page.tsx" | head -120 | tail -90
Length of output: 3481
🏁 Script executed:
#!/bin/bash
# Search for structuredData definition in the file
rg -n "structuredData" "docs/src/app/(home)/tools/seo-title-generator/page.tsx" -A5 -B5Length of output: 647
🏁 Script executed:
#!/bin/bash
# Check for Biome configuration
fd -e json -e js -e toml | xargs rg -l "biome|dangerouslySetInnerHtml" --type=json --type=js --type=toml 2>/dev/null | head -5Length of output: 10
🏁 Script executed:
#!/bin/bash
# Look for biome config file
fd "biome\." -type f | head -5Length of output: 170
---
🏁 Script executed:
```shell
#!/bin/bash
# Check biome configuration for security rules related to dangerouslySetInnerHtml
cat "biome.json"
Length of output: 1774
🏁 Script executed:
#!/bin/bash
# Check lines 111-113 specifically to see exact content
sed -n '110,115p' "docs/src/app/(home)/tools/seo-title-generator/page.tsx"Length of output: 223
🏁 Script executed:
#!/bin/bash
# Search for any other uses of dangerouslySetInnerHTML in the same file
rg -n "dangerouslySetInnerHTML" "docs/src/app/(home)/tools/seo-title-generator/page.tsx" -B2 -A2Length of output: 188
---
🌐 Web query:
```web
Biome security rule noDangerouslySetInnerHtml lint rule
💡 Result:
Summary
- Biome lint rule: security/noDangerouslySetInnerHtml — prevents use of React's dangerouslySetInnerHTML (XSS risk). Default severity: error; recommended/enabled by default. [1][2][3]
How to configure
- biome.json example:
{
"linter": {
"rules": {
"security": { "noDangerouslySetInnerHtml": "error" }
}
}
} - There is also security/noDangerouslySetInnerHtmlWithChildren to disallow using both children and dangerouslySetInnerHTML. [1][2]
Example (invalid)
- Reported as lint/security/noDangerouslySetInnerHtml because it can expose users to XSS. [1]
Notes
- Rule maps to the same check as ESLint's react/no-danger. [1]
Sources
[1] Biome rule page: noDangerouslySetInnerHtml.
[2] Biome rules index (security section).
[3] Biome (es) rule page indicating recommended/default.
Correct line reference: JSON-LD injection at line 112 only (not 111-113)
The suggestion is valid: structuredData is static and safe from XSS, so adding a biome-ignore comment for security/noDangerouslySetInnerHtml is appropriate. However, there is only one instance in this file at line 112, not multiple locations. Update the fix to:
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is static JSON-LD defined in this file */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>Applied at line 112 only.
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/seo-title-generator/page.tsx at line 112, update
the single JSON-LD script injection to include the biome-ignore lint comment for
security/noDangerouslySetInnerHtml and ensure it is applied only at that
location; specifically, place the comment immediately above the script tag and
keep the script as type="application/ld+json" with dangerouslySetInnerHTML using
JSON.stringify(structuredData), using this exact inline comment: {/*
biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is static
JSON-LD defined in this file */}.
- Updated multiple tool pages to utilize the new ToolCTA component for call-to-action sections, enhancing consistency across the application. - Changed URLs in metadata from the documentation domain to the main site for improved SEO and user navigation. - Adjusted Breadcrumbs for better readability and structure in the UI.
There was a problem hiding this 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
♻️ Duplicate comments (5)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (1)
9-41: Standardize canonical domain for this tool with other pagesHere both
metadata.openGraph.url/alternates.canonicaland the JSON-LDurlfields usehttps://rybbit.io/tools/funnel-visualizer, while other tools in this PR usehttps://rybbit.com/.... It’s worth deciding on a single canonical host for all tools and aligning metadata + JSON-LD across pages (and the sitemap) to avoid split SEO/analytics signals.Also applies to: 48-52
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (1)
9-37: Align marketing ROI page domain with the chosen canonical hostThis page uses
https://rybbit.io/tools/marketing-roi-calculatorin bothmetadataand the JSON-LDurl, while some other tools in this PR usehttps://rybbit.com/.... To avoid SEO/analytics fragmentation and conflicting canonicals, pick a single canonical domain for all tools and updatemetadata+ JSON-LD (and sitemap) accordingly.Also applies to: 46-48
docs/src/app/(home)/tools/analytics-detector/page.tsx (1)
9-40: Confirmrybbit.comis the intended canonical host for this toolAll metadata/JSON-LD URLs here point to
https://rybbit.com/tools/analytics-detector, while other tools in this PR usehttps://rybbit.io/.... Previous reviews already flagged mixed domains across tool pages; it’d be good to confirm which host actually serves/tools/*and then standardizeopenGraph.url,alternates.canonical, and JSON-LD URLs across all tools to match.Also applies to: 46-51, 57-61
docs/src/app/(home)/tools/utm-builder/page.tsx (1)
24-40: Unify domains between metadata and JSON-LD author URLIn this file,
metadata.openGraph.url,alternates.canonical, and JSON-LDurlusehttps://rybbit.com/tools/utm-builder, butstructuredData.author.urlpoints tohttps://rybbit.io. That split host within a single page can confuse search engines and analytics attribution. Once you’ve decided on the canonical domain for tools, update all of these URLs to use the same host.Also applies to: 47-50, 57-60
docs/src/app/(home)/tools/og-tag-generator/page.tsx (1)
42-47: Re-apply JSON‑LDdangerouslySetInnerHTMLsuppression for lintersThis is the same static JSON‑LD pattern previously discussed in review:
structuredDatais a local constant, and the only consumer is the<script>tag, so there’s no XSS risk. Biome and ast‑grep, however, still flag thedangerouslySetInnerHTMLusage.Reiterating the earlier fix (now applied on other tool pages too):
- <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */} + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} + />This keeps the security rule intact for real risks while documenting this exception.
Also applies to: 117-117
🧹 Nitpick comments (12)
docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
4-35: ToolCTA implementation looks good; optional prop typing tweak for flexibilityThe CTA logic and TrackedButton wiring look correct. If you ever need inline markup in
titleordescription, you could widen them toReactNodewithout breaking existing string usages.-import { TrackedButton } from "@/components/TrackedButton"; +import { TrackedButton } from "@/components/TrackedButton"; +import type { ReactNode } from "react"; interface ToolCTAProps { - title: string; - description: string; + title: ReactNode; + description: ReactNode; eventLocation: string; buttonText?: string; }docs/src/components/RelatedTools.tsx (1)
4-102: RelatedTools behavior is solid; dedupe the category union typeFiltering/prioritization logic looks correct and matches the intended UX. To avoid the
"seo" | "analytics" | "privacy"union drifting in multiple places, you could centralize it in a shared alias.-interface Tool { +type ToolCategory = "seo" | "analytics" | "privacy"; + +interface Tool { name: string; description: string; href: string; - category: "seo" | "analytics" | "privacy"; + category: ToolCategory; } interface RelatedToolsProps { currentToolHref: string; - category?: "seo" | "analytics" | "privacy"; + category?: ToolCategory; maxTools?: number; }docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx (1)
45-121: Deduplicate FAQ content between JSON-LD and the AccordionThe FAQ Q&A pairs are manually duplicated in both
structuredData.@graph[1].mainEntityand the Accordion JSX, which makes future edits easy to miss. You can centralize them in a singlefaqItemsarray and derive both the JSON-LD and the Accordion from that, reusing the same JSON-LD injection pattern (and any lint ignore/helper) as on the Bounce Rate page.Example sketch:
const faqItems = [ { question: "Do I need a privacy policy for my website?", answer: "Yes, most websites need a privacy policy. It's legally required if you collect personal data...", }, // ... ]; const structuredData = { "@context": "https://schema.org", "@graph": [ { "@type": "WebApplication", // ... }, { "@type": "FAQPage", mainEntity: faqItems.map(item => ({ "@type": "Question", name: item.question, acceptedAnswer: { "@type": "Answer", text: item.answer }, })), }, ], };and in JSX:
{faqItems.map((item, index) => ( <AccordionItem key={index} value={`item-${index + 1}`}> <AccordionTrigger>{item.question}</AccordionTrigger> <AccordionContent>{item.answer}</AccordionContent> </AccordionItem> ))}This keeps on-page content and JSON-LD in sync while still using a static
structuredDataobject for thedangerouslySetInnerHTMLJSON-LD script as discussed for the Bounce Rate page.Also applies to: 125-126, 427-512
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (1)
41-43: JSON-LD injection pattern is safe here but clashes with lint rulesUsing
dangerouslySetInnerHTMLwith a compile-timestructuredDataobject is a standard way to emit JSON-LD and is effectively safe as long as no user input flows into it, which is the case here. To satisfy Biome while keeping this pattern, you can add a targeted ignore comment or wrap this in a small<JsonLd>helper that centralizes thedangerouslySetInnerHTMLusage./* biome-ignore lint/security/noDangerouslySetInnerHtml -- structuredData is static JSON-LD */ <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />Also applies to: 61-107, 111-113
docs/src/app/(home)/tools/components/index.ts (1)
1-1: ToolCTA barrel export is correct; consider consolidating importsRe-exporting
ToolCTAhere is fine and makes the barrel more useful. You might optionally update tool pages to import from"../components"instead of"../components/ToolCTA"for consistency, but the current setup is functionally correct.docs/src/app/(home)/tools/funnel-visualizer/page.tsx (1)
44-112: HoistschemaDataoutside the component to avoid per-render allocation
schemaDatais static and doesn’t depend on props or state. Moving it to a top-levelconst(like you do in some other pages) will avoid recreating the object on every render, even if the impact is small for this mostly-static page.docs/src/app/(home)/tools/utm-builder/page.tsx (1)
42-45: JSON-LD injection viadangerouslySetInnerHTMLis static; consider adding a Biome ignore/helperHere
structuredDatais a static in-module object and you injectJSON.stringify(structuredData)into a<script type="application/ld+json">. From a security standpoint this is fine (no user input), but Biome flagsdangerouslySetInnerHTMLwithlint/security/noDangerouslySetInnerHtml.If Biome is gating CI, consider either:
- Adding a one-line ignore with rationale:
// biome-ignore lint/security/noDangerouslySetInnerHtml: injecting static JSON-LD schema data <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
- Or wrapping this pattern in a small
<JsonLdScript data={...} />helper that centralizes the ignore, then reusing it in other tool pages that embed JSON-LD.This same pattern appears on the other tool pages using JSON-LD, so you can apply the same solution there.
Also applies to: 112-113
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx (1)
44-47: Suppress lints for static JSON‑LDdangerouslySetInnerHTMLusage
structuredDatais a static object defined in this file, so injecting it as JSON‑LD is effectively safe. The Biome and ast‑grep warnings are still going to fail CI unless you explicitly suppress this one known‑safe use.I suggest adding a targeted ignore comment and formatting the script on multiple lines:
- <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */} + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} + />If you also enforce the ast‑grep rule, you may want to narrow it to non‑JSON‑LD cases rather than this pattern.
Also applies to: 115-115
docs/src/app/(home)/tools/ctr-calculator/page.tsx (1)
44-47: Suppress Biome/security lint for static JSON‑LD scriptAs with the other tool pages,
structuredDatahere is a local, static JSON object, so usingdangerouslySetInnerHTMLfor the JSON‑LD<script>is safe, but Biome is still flagging it as a security error.You can make this intent explicit and keep CI green with an inline ignore:
- <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */} + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} + />Consider aligning any ast‑grep rule with this exception as well so JSON‑LD inserts don’t cause noise.
Also applies to: 117-117
docs/src/app/(home)/tools/sample-size-calculator/page.tsx (2)
5-10: Use a type‑only import forMetadatafor consistency and clarityHere
Metadatais only used as a type annotation, but it’s imported as a value:import { Metadata } from "next";To align with Next.js patterns (and your other tool pages), consider switching to a type‑only import:
-import { Metadata } from "next"; +import type { Metadata } from "next";This makes the intent explicit and avoids any chance of a runtime named import being preserved by the compiler.
43-47: Add explicit ignore for static JSON‑LDdangerouslySetInnerHTML
structuredDatais defined locally as a plain object and never includes user input, so usingdangerouslySetInnerHTMLto emit JSON‑LD is safe. Biome still treats this as a security error.You can document the intent and silence the rule with:
- <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */} + <script + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} + />Adjust any ast‑grep rule similarly if it’s part of your CI.
Also applies to: 126-126
docs/src/app/(home)/tools/og-tag-generator/page.tsx (1)
5-10: Preferimport type { Metadata }instead of a value importHere too
Metadatais only used as a type for themetadataexport, but it’s imported as a runtime value:import { Metadata } from "next";For consistency with other pages and Next.js docs, consider:
-import { Metadata } from "next"; +import type { Metadata } from "next";This keeps the import tree clean and clearly marks
Metadataas type‑only.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx(1 hunks)docs/src/app/(home)/tools/analytics-detector/page.tsx(3 hunks)docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/components/ToolCTA.tsx(1 hunks)docs/src/app/(home)/tools/components/index.ts(1 hunks)docs/src/app/(home)/tools/ctr-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/funnel-visualizer/page.tsx(1 hunks)docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/og-tag-generator/page.tsx(1 hunks)docs/src/app/(home)/tools/sample-size-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/traffic-value-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/utm-builder/page.tsx(1 hunks)docs/src/app/\(home\)/tools/ai-privacy-policy-generator/page.tsx(1 hunks)docs/src/app/blog/[...slug]/page.tsx(2 hunks)docs/src/components/RelatedTools.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx (7)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (1)
metadata(9-37)docs/src/app/(home)/oss-friends/page.tsx (1)
metadata(105-108)docs/src/app/(home)/tools/seo-title-generator/page.tsx (1)
metadata(8-37)docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/sample-size-calculator/SampleSizeForm.tsx (1)
SampleSizeForm(5-178)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-152)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
docs/src/components/TrackedButton.tsx (1)
TrackedButton(16-31)
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/bounce-rate-calculator/BounceRateForm.tsx (1)
BounceRateForm(17-233)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/marketing-roi-calculator/MarketingROIForm.tsx (1)
MarketingROIForm(12-211)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
docs/src/app/(home)/tools/og-tag-generator/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/og-tag-generator/OGTagForm.tsx (1)
OGTagForm(16-205)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
docs/src/app/(home)/tools/utm-builder/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/utm-builder/UTMBuilderForm.tsx (1)
UTMBuilderForm(8-233)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
docs/src/app/(home)/tools/ctr-calculator/page.tsx (4)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/ctr-calculator/CTRCalculatorForm.tsx (1)
CTRCalculatorForm(15-158)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-152)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
🪛 ast-grep (0.40.0)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx
[warning] 125-125: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/\(home\)/tools/ai-privacy-policy-generator/page.tsx
[warning] 124-124: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx
[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx
[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx
[warning] 115-115: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/og-tag-generator/page.tsx
[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx
[warning] 125-125: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/utm-builder/page.tsx
[warning] 112-112: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/analytics-detector/page.tsx
[warning] 113-113: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx
[warning] 114-114: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/ctr-calculator/page.tsx
[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx
[error] 126-126: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/\(home\)/tools/ai-privacy-policy-generator/page.tsx
[error] 125-125: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx
[error] 112-112: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx
[error] 112-112: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx
[error] 116-116: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/og-tag-generator/page.tsx
[error] 117-117: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx
[error] 126-126: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/utm-builder/page.tsx
[error] 113-113: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/analytics-detector/page.tsx
[error] 114-114: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx
[error] 115-115: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/ctr-calculator/page.tsx
[error] 117-117: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Client Images (ubuntu-latest, linux/amd64)
- GitHub Check: Build Client Images (ubuntu-24.04-arm, linux/arm64)
🔇 Additional comments (10)
docs/src/app/blog/[...slug]/page.tsx (2)
171-174: LGTM - Formatting improvement.The Pre component props are now spread across multiple lines for better readability. No functional changes.
22-22: Domain migration change is consistent across the codebase—no issues detected in code.Verification shows:
- No references to
docs.rybbit.ioexist anywhere in the codebasehttps://rybbit.comis consistently used throughout the documentation site (layout metadata, robots.ts, sitemap.ts, blog pages, and all tool pages)- Line 22 change aligns with the established domain strategy
The code change is correct and consistent. However, redirect configuration from the old domain (if any) cannot be verified through code inspection alone.
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx (1)
7-527: AI Privacy Policy Generator page wiring and content look consistentMetadata, JSON-LD, the PrivacyPolicyForm, explanatory sections, FAQ, and the bottom signup CTA are all coherently wired; I don’t see functional or SEO issues in this version.
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (1)
114-369: Bounce Rate Calculator layout and props are correctly wiredBreadcrumbs items, BounceRateForm usage, explanatory sections, RelatedTools (
/tools/bounce-rate-calculator,category="analytics"), and the ToolCTA all look consistent and correct; I don’t see issues with slugs, URLs, or copy here.docs/src/app/\(home\)/tools/ai-privacy-policy-generator/page.tsx (6)
1-5: AI summary mentions missing components.The AI summary states that Breadcrumbs, RelatedTools, and ToolCTA components are integrated and imported, but these are not present in the actual code. The component directly implements the CTA section (lines 509-527) instead of using a ToolCTA component, and breadcrumbs/related tools are absent.
7-42: Comprehensive metadata configuration.The metadata export is well-structured with detailed SEO optimization including title, description, keywords, OpenGraph, Twitter cards, and canonical URL. This will significantly improve search engine visibility and social media sharing.
125-125: Safe usage of dangerouslySetInnerHTML for JSON-LD.The static analysis warning can be safely ignored. This usage injects JSON-LD structured data from a compile-time constant (
structuredData) defined in the same file. SinceJSON.stringify()produces JSON (not executable code) and no user input is involved, there is no XSS risk. This is the standard pattern for embedding schema.org structured data.
122-507: Well-structured and comprehensive content.The component is logically organized with clear sections covering essential privacy policy topics (GDPR, CCPA, required sections, legal considerations). The FAQ implementation using Accordion components provides good UX, and the disclaimer (lines 147-153) appropriately sets expectations about legal review requirements.
508-527: Effective CTA with proper event tracking.The CTA section effectively promotes Rybbit's analytics while maintaining thematic consistency with the privacy-focused tool. The TrackedButton implementation correctly includes event name and location properties for conversion tracking.
44-120: This review comment is incorrect. The domain split betweenrybbit.ioandrybbit.comis intentional, not an inconsistency.From the codebase search results, this pattern is consistent across all tool pages: the Organization author URL in schema.org uses
https://rybbit.io(the corporate/brand domain), while the tool pages, canonical URLs, and marketing content usehttps://rybbit.com(the documentation/marketing domain). This is a deliberate multi-domain strategy reflected throughout the codebase, includingapp.rybbit.iofor the SaaS application andrybbit.comfor marketing and documentation. The code change does not introduce any inconsistency.Likely an incorrect or invalid review comment.
- Deleted the AI Privacy Policy Generator tool page and its associated components, including the PrivacyPolicyForm and related metadata. - Updated the tools list to reflect the removal of the AI Privacy Policy Generator, enhancing clarity and focus on remaining tools. - Adjusted educational content across various tools for improved readability and consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (3)
docs/src/app/(home)/tools/og-tag-generator/page.tsx (1)
117-117: Previous security lint suppression suggestion not yet applied.The biome-ignore comment to suppress the
lint/security/noDangerouslySetInnerHtmlwarning was suggested in a previous review but hasn't been added yet. This will continue to cause static analysis tool failures.Apply this diff to add the suppression comment:
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: structuredData is a static, local JSON-LD object */} <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />docs/src/app/(home)/tools/traffic-value-calculator/page.tsx (2)
88-88: Fix mathematical error in FAQ structured data (still present from previous review)The FAQ states "$2.81 per visitor" but the correct calculation is:
- 50,000 × 2.5% × $75 × 30% = $28,125 total profit
- $28,125 ÷ 50,000 = $0.56 per visitor
This error contradicts the correct step-by-step calculation shown at line 170 and was flagged in the previous review but remains unfixed.
Apply this diff to correct the FAQ structured data:
- text: "Traffic value is calculated as: Monthly Visitors × Conversion Rate × Average Order Value × Profit Margin. For example, 50,000 visitors × 2.5% conversion × $75 AOV × 30% margin = $2.81 per visitor in monthly profit.", + text: "Traffic value is calculated as: Monthly Visitors × Conversion Rate × Average Order Value × Profit Margin. For example, 50,000 visitors × 2.5% conversion × $75 AOV × 30% margin = approximately $0.56 per visitor in monthly profit.",
153-156: Fix mathematical errors in narrative example (still present from previous review)The example contains two mathematical errors that contradict the correct step-by-step calculation at line 170:
- "$2.81 per visitor" should be "$0.56 per visitor"
- Calculation: $28,125 profit ÷ 50,000 visitors = $0.56
- "nearly $1,400" should be "approximately $2,800"
- 10% increase: 55,000 visitors generates $30,937.50 profit
- Increase: $30,937.50 - $28,125 = $2,812.50
These errors were flagged in the previous review but remain unfixed.
Apply this diff to correct the narrative:
- For example, if you have 50,000 monthly visitors, a 2.5% conversion rate, an average order value of $75, - and a 30% profit margin, your traffic value would be approximately $2.81 per visitor in monthly profit. - This means increasing your traffic by just 10% could add nearly $1,400 to your monthly profit. + For example, if you have 50,000 monthly visitors, a 2.5% conversion rate, an average order value of $75, + and a 30% profit margin, your traffic value would be approximately $0.56 per visitor in monthly profit. + This means increasing your traffic by just 10% could add approximately $2,800 to your monthly profit.
🧹 Nitpick comments (3)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx (1)
68-120: Consider centralizing FAQ content to drive both JSON-LD and the AccordionYou currently duplicate the six FAQ Q&As in both
structuredData.FAQPage.mainEntityand the Accordion items. This is fine functionally, but it’s easy for them to drift out of sync over time.You could define a single
const faqItems = [...]data structure (id, question, answer) and:
- Map it into
mainEntityfor JSON-LD.- Map it into
<AccordionItem>s for the UI.That would keep content consistent and make future edits cheaper.
Also applies to: 315-399
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (2)
44-112: Consider moving static schema data outside the component.The JSON-LD structured data is well-formed and follows schema.org guidelines correctly. However, since
schemaDatais static and doesn't depend on props or state, defining it inside the component causes it to be recreated on every render.Move the schema data outside the component function for better performance:
+const schemaData = { + "@context": "https://schema.org", + "@graph": [ + // ... (entire schemaData object) + ], +}; + export default function FunnelVisualizerPage() { - const schemaData = { - "@context": "https://schema.org", - "@graph": [ - // ... - ], - };
116-116: Consider usingnext/scriptfor JSON-LD injection.The
dangerouslySetInnerHTMLusage here is safe sinceschemaDatais a local object without user input. However, Next.js provides a more idiomatic approach using theScriptcomponent.Replace the script tag with Next.js Script component:
+import Script from "next/script";-<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} /> +<Script + id="schema-funnel-visualizer" + type="application/ld+json" + dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }} +/>This approach provides better loading optimization and follows Next.js best practices while maintaining the same functionality.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx(0 hunks)docs/src/app/(home)/tools/ctr-calculator/page.tsx(2 hunks)docs/src/app/(home)/tools/funnel-visualizer/page.tsx(1 hunks)docs/src/app/(home)/tools/og-tag-generator/page.tsx(1 hunks)docs/src/app/(home)/tools/page-speed-calculator/page.tsx(2 hunks)docs/src/app/(home)/tools/page.tsx(1 hunks)docs/src/app/(home)/tools/sample-size-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/traffic-value-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/utm-builder/page.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- docs/src/app/(home)/tools/ai-privacy-policy-generator/page.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (5)
docs/src/app/(home)/tools/page.tsx (1)
metadata(18-22)docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/funnel-visualizer/FunnelVisualizerForm.tsx (1)
FunnelVisualizerForm(20-230)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-152)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
docs/src/app/(home)/tools/utm-builder/page.tsx (3)
docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/utm-builder/UTMBuilderForm.tsx (1)
UTMBuilderForm(8-233)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
docs/src/app/(home)/tools/page-speed-calculator/page.tsx (5)
docs/src/app/(home)/tools/page.tsx (1)
metadata(18-22)docs/src/app/(home)/tools/seo-title-generator/page.tsx (1)
metadata(8-37)docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/page-speed-calculator/PageSpeedForm.tsx (1)
PageSpeedForm(22-365)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-152)
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx (4)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (1)
metadata(9-37)docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/app/(home)/tools/traffic-value-calculator/TrafficValueForm.tsx (1)
TrafficValueForm(7-286)docs/src/app/(home)/tools/components/ToolCTA.tsx (1)
ToolCTA(11-38)
🪛 ast-grep (0.40.0)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx
[warning] 115-115: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx
[warning] 125-125: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/utm-builder/page.tsx
[warning] 112-112: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/page-speed-calculator/page.tsx
[warning] 118-118: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/og-tag-generator/page.tsx
[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/ctr-calculator/page.tsx
[warning] 116-116: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx
[warning] 114-114: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx
[error] 116-116: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx
[error] 126-126: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/utm-builder/page.tsx
[error] 113-113: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/page-speed-calculator/page.tsx
[error] 119-119: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/og-tag-generator/page.tsx
[error] 117-117: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/ctr-calculator/page.tsx
[error] 117-117: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx
[error] 115-115: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Client Images (ubuntu-24.04-arm, linux/arm64)
- GitHub Check: Build Client Images (ubuntu-latest, linux/amd64)
🔇 Additional comments (23)
docs/src/app/(home)/tools/sample-size-calculator/page.tsx (2)
9-41: SEO metadata and hero/tool framing look solidThe metadata block is comprehensive (title, description, keywords, OG, Twitter, canonical) and matches the visible hero copy and tool purpose. This should play nicely with search + social previews without any obvious inconsistencies.
Also applies to: 138-153
43-121: JSON-LD viadangerouslySetInnerHTMLis acceptable here but should remain strictly staticUsing a
<script type="application/ld+json">withdangerouslySetInnerHTMLis a standard pattern for JSON-LD, and in this case thestructuredDataobject is fully hard-coded in this file, so there’s no realistic XSS surface right now.The main thing to watch long-term is that nothing user-controlled or request-scoped ever gets interpolated into
structuredData. If that ever happens, it should be sanitized or encoded before stringification, or built on the server and injected via a safer channel.If your lint rules allow it, you may want to add a local disable + comment clarifying that this is intentional, schema-only usage so future changes don’t accidentally weaken the safety invariants.
Also applies to: 123-127
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (2)
1-7: LGTM!All imports are properly utilized throughout the component and follow Next.js conventions.
114-348: LGTM! Well-structured content-rich page.The page structure effectively combines:
- Clear navigation with Breadcrumbs
- Informative header section
- Interactive tool integration via FunnelVisualizerForm
- Comprehensive educational content
- SEO-friendly FAQ section that mirrors the JSON-LD schema
- Related tools discovery
- Strong CTA using ToolCTA component
The modular composition using shared components (Breadcrumbs, RelatedTools, ToolCTA) promotes maintainability and consistency across tool pages.
docs/src/app/(home)/tools/traffic-value-calculator/page.tsx (4)
1-5: LGTM!The imports are well-organized and align with the component-based refactoring approach used across other tool pages.
115-115: Static analysis false positive: dangerouslySetInnerHTML is safe hereThe static analysis warning about
dangerouslySetInnerHTMLcan be safely ignored in this context. The structured data is hardcoded and contains no user input, making this a standard and secure pattern for injecting JSON-LD schema markup in Next.js applications.
144-272: Excellent educational content structureThe comprehensive educational sections (What is Traffic Value, How to Calculate, Factors Affecting, Strategies to Maximize, Industry Benchmarks) provide substantial value to users. The step-by-step calculation at line 170 correctly demonstrates the $0.56 per visitor result, making it a reliable reference once the inconsistencies in earlier sections are corrected.
117-283: Well-structured component refactoringThe page successfully follows the content-rich educational pattern seen in other tool pages, with:
- Clean separation of concerns (form logic in TrafficValueForm)
- Breadcrumbs for navigation
- Comprehensive educational content
- Related tools and CTA sections
This aligns well with the PR objectives to enhance tool pages for better SEO and discoverability.
docs/src/app/(home)/tools/utm-builder/page.tsx (7)
1-5: LGTM!The imports are correctly structured and align with Next.js 15 conventions. All imported components are properly referenced in the code.
24-40: LGTM!The OpenGraph, Twitter card, and canonical URL metadata are properly structured and follow Next.js 15 conventions. Good use of SEO best practices.
42-108: Verify URL consistency:rybbit.iovsrybbit.com.The structured data looks well-formed and follows schema.org standards. However, note that line 59 uses
https://rybbit.iofor the organization URL, while the rest of the metadata usesrybbit.com. Ensure this is intentional (e.g., if the company site is on .io while the product is on .com).
113-113: Static analysis false positive: JSON-LD injection is safe.The static analysis tools flagged
dangerouslySetInnerHTMLas a potential XSS risk. However, this is a false positive because:
- The content is static and hardcoded (
structuredDataobject defined in this file)- No user input is involved
JSON.stringifyensures proper encoding- This is the standard method for adding JSON-LD structured data in React/Next.js
No action needed.
115-136: LGTM!The page structure, breadcrumbs, header, and form component integration are well-organized. The responsive layout and typography hierarchy follow good practices.
138-281: LGTM!The educational content is comprehensive, well-structured, and follows proper semantic HTML practices. The sections cover all essential aspects of UTM parameters with practical examples and best practices. Dark mode styling is properly implemented throughout.
282-290: LGTM!The RelatedTools and ToolCTA components are properly integrated with appropriate props. The category classification and event tracking location are well-chosen.
docs/src/app/(home)/tools/page.tsx (1)
133-133: LGTM: Styling simplification is consistent with PR goals.The removal of gradient background classes aligns with the PR's objective to simplify styling across tool pages.
docs/src/app/(home)/tools/page-speed-calculator/page.tsx (4)
6-45: LGTM: Comprehensive SEO metadata.The metadata export follows Next.js 15 best practices with proper title, description, keywords, OpenGraph, Twitter cards, and canonical URL.
48-119: LGTM: Safe usage of dangerouslySetInnerHTML for structured data.The
dangerouslySetInnerHTMLusage is safe here because theschemaobject is a static constant defined in the component, not user-generated content. This is the standard pattern for injecting JSON-LD structured data for SEO purposes. The structured data correctly follows schema.org standards for WebApplication and FAQPage types.
136-182: LGTM: Valuable educational content enhances page quality.The educational sections provide context and actionable guidance for users, transforming the page from a simple calculator into a comprehensive resource. Content is well-structured and relevant.
185-186: LGTM: Component-based architecture improves maintainability.The extraction of calculation logic into
PageSpeedFormand addition ofRelatedToolsfollows good separation of concerns and enhances discoverability.docs/src/app/(home)/tools/ctr-calculator/page.tsx (3)
117-117: LGTM: Safe usage of dangerouslySetInnerHTML for structured data.The
dangerouslySetInnerHTMLusage is safe becausestructuredDatais a static constant, not user-generated content. This is the standard pattern for injecting JSON-LD structured data for SEO.
141-294: LGTM: Comprehensive educational content adds significant value.The extensive educational sections covering CTR concepts, benchmarks, and optimization techniques transform this from a simple calculator into a valuable marketing resource. Content is well-organized and actionable.
121-123: LGTM: Component-based refactor improves architecture.The use of
Breadcrumbs,CTRCalculatorForm,RelatedTools, andToolCTAcomponents follows good separation of concerns and aligns with the PR's architectural improvements across tool pages.Also applies to: 139-139, 363-363, 366-370
| export const metadata: Metadata = { | ||
| title: "Free CTR Calculator | Click-Through Rate Calculator for Marketing", | ||
| description: | ||
| "Calculate your click-through rate (CTR) and compare it to industry benchmarks. Free CTR calculator for email, PPC, organic search, and social media campaigns. Learn CTR formulas, industry standards, and how to improve your CTR.", | ||
| keywords: [ | ||
| "CTR calculator", | ||
| "click-through rate calculator", | ||
| "ctr formula", | ||
| "click through rate", | ||
| "marketing metrics", | ||
| "industry benchmarks", | ||
| "CTR improvement", | ||
| "ad performance", | ||
| "conversion metrics", | ||
| "marketing analytics", | ||
| ], | ||
| openGraph: { | ||
| title: "Free CTR Calculator | Click-Through Rate Calculator for Marketing", | ||
| description: | ||
| "Calculate your CTR and see how your campaigns compare to industry benchmarks across email, PPC, organic search, and social media.", | ||
| type: "website", | ||
| url: "https://rybbit.com/tools/ctr-calculator", | ||
| siteName: "Rybbit Documentation", | ||
| }, | ||
| twitter: { | ||
| card: "summary_large_image", | ||
| title: "Free CTR Calculator | Click-Through Rate Calculator for Marketing", | ||
| description: | ||
| "Calculate your CTR and compare to industry benchmarks. Understand what's a good click-through rate for your channel.", | ||
| }, | ||
| alternates: { | ||
| canonical: "https://rybbit.com/tools/ctr-calculator", | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify domain consistency across tool pages.
The metadata uses rybbit.com domain (lines 30, 40), while the page-speed-calculator tool uses rybbit.io consistently. This domain inconsistency should be resolved to ensure proper SEO and canonical URL handling.
🏁 Script executed:
#!/bin/bash
# Description: Check domain usage consistency across tool pages metadata
# Search for domain usage in metadata exports
rg -n "rybbit\.(com|io)" --type=ts -g "**/tools/**/page.tsx" -C 2Length of output: 28492
Update domain consistency across all tool metadata.
Verification confirms domain inconsistency affects multiple files, not just the CTR calculator:
- CTR calculator (lines 30, 40, 49, 52): uses
rybbit.comfor tool URLs butrybbit.iofor Organization (line 62) - Other files similarly affected: bounce-rate-calculator, traffic-value-calculator, and others mix
rybbit.comandrybbit.iowithin the same metadata object - Pattern: Some tools consistently use
rybbit.io(page-speed-calculator, marketing-roi-calculator, funnel-visualizer), while others userybbit.com(privacy-policy-builder, og-tag-generator, meta-description-generator, seo-title-generator, sample-size-calculator, utm-builder, analytics-detector)
Establish a single domain convention across all tool pages for canonical URLs, openGraph URLs, and schema URLs to maintain SEO integrity and prevent duplicate content issues.
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/ctr-calculator/page.tsx around lines 9 to 42, the
metadata mixes rybbit.com and rybbit.io; update the metadata URLs
(openGraph.url, alternates.canonical, and any other URL fields) to use the
agreed canonical domain rybbit.io so they match the organization info elsewhere,
and then search other tool pages and replace any remaining rybbit.com
occurrences with rybbit.io to maintain a single domain convention across all
tool metadata.
| const structuredData = { | ||
| "@context": "https://schema.org", | ||
| "@graph": [ | ||
| { | ||
| "@type": "WebApplication", | ||
| "@id": "https://rybbit.com/tools/ctr-calculator#webapp", | ||
| name: "CTR Calculator", | ||
| description: "Free tool to calculate click-through rate and compare with industry benchmarks", | ||
| url: "https://rybbit.com/tools/ctr-calculator", | ||
| applicationCategory: "Utility", | ||
| offers: { | ||
| "@type": "Offer", | ||
| price: "0", | ||
| priceCurrency: "USD", | ||
| }, | ||
| author: { | ||
| "@type": "Organization", | ||
| name: "Rybbit", | ||
| url: "https://rybbit.io", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "FAQPage", | ||
| "@id": "https://rybbit.com/tools/ctr-calculator#faqpage", | ||
| mainEntity: [ | ||
| { | ||
| "@type": "Question", | ||
| name: "What is CTR (Click-Through Rate)?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "CTR is the percentage of people who click on your ad or link after seeing it. It's calculated by dividing the number of clicks by the number of impressions and multiplying by 100. For example, if your ad was shown 10,000 times and received 300 clicks, your CTR is 3%.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "What is a good CTR by channel?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "Average CTRs vary significantly by channel. Search ads average 3.17%, email marketing 2.6%, e-commerce 2.69%, B2B 2.41%, social media 0.90%, and display ads 0.46%. Your target CTR should be benchmarked against your specific channel and industry.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "How do I calculate CTR?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "To calculate CTR, use this formula: CTR = (Clicks / Impressions) × 100. For example, if your ad received 500 clicks from 50,000 impressions, your CTR would be (500 / 50,000) × 100 = 1%. Use our CTR calculator above to compute this automatically.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "How can I improve my CTR?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "To improve CTR: write compelling ad copy with clear value propositions, use strong calls-to-action, improve audience targeting, test different headlines and creatives, ensure ad relevance to search intent, add ad extensions, and improve landing page relevance. Monitor results to identify what works best.", | ||
| }, | ||
| }, | ||
| { | ||
| "@type": "Question", | ||
| name: "Why is my CTR different from competitor benchmarks?", | ||
| acceptedAnswer: { | ||
| "@type": "Answer", | ||
| text: "CTR varies based on industry, product type, audience quality, ad positioning, seasonal factors, and competition. Your CTR may legitimately differ from benchmarks due to your unique market position, business model, or targeting strategy. Focus on improving your own CTR over time rather than exactly matching benchmarks.", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Domain inconsistency also present in structured data.
The structured data contains the same domain inconsistency: tool URLs use rybbit.com (lines 49, 52, 67) while the author organization URL uses rybbit.io (line 62). This should be aligned with the decision made for the metadata URLs.
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/ctr-calculator/page.tsx around lines 44 to 112
there is a domain mismatch in the structuredData: the tool @id and url entries
use "rybbit.com" while the author organization url uses "rybbit.io"; pick the
canonical metadata domain used across the project (whichever was chosen in
metadata) and update all occurrences in this structuredData block so the @id and
url values use that same domain (make the tool URLs match the chosen domain and
keep all absolute URLs consistent).
| export const metadata: Metadata = { | ||
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | ||
| description: | ||
| "Visualize your conversion funnel step-by-step. Input visitor counts at each stage to identify where you're losing customers. Analyze drop-off rates and optimize your funnel for better conversions.", | ||
| keywords: [ | ||
| "funnel visualizer", | ||
| "conversion funnel", | ||
| "funnel analysis", | ||
| "conversion rate optimization", | ||
| "sales funnel", | ||
| "funnel metrics", | ||
| "drop-off analysis", | ||
| "customer journey", | ||
| "conversion tracking", | ||
| "funnel optimization", | ||
| ], | ||
| openGraph: { | ||
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | ||
| description: | ||
| "Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.", | ||
| type: "website", | ||
| url: "https://rybbit.io/tools/funnel-visualizer", | ||
| }, | ||
| twitter: { | ||
| card: "summary_large_image", | ||
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | ||
| description: | ||
| "Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.", | ||
| }, | ||
| alternates: { | ||
| canonical: "https://rybbit.io/tools/funnel-visualizer", | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add images to social media metadata.
The metadata is comprehensive, but both openGraph and twitter configurations are missing image properties. Twitter card type summary_large_image specifically requires an image, and OpenGraph images significantly improve social sharing appearance.
Add image properties to enhance social media previews:
openGraph: {
title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool",
description:
"Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.",
type: "website",
url: "https://rybbit.io/tools/funnel-visualizer",
+ images: [{
+ url: "https://rybbit.io/og-images/funnel-visualizer.png",
+ width: 1200,
+ height: 630,
+ alt: "Funnel Visualizer Tool"
+ }],
},
twitter: {
card: "summary_large_image",
title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool",
description:
"Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.",
+ images: ["https://rybbit.io/og-images/funnel-visualizer.png"],
},📝 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.
| export const metadata: Metadata = { | |
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | |
| description: | |
| "Visualize your conversion funnel step-by-step. Input visitor counts at each stage to identify where you're losing customers. Analyze drop-off rates and optimize your funnel for better conversions.", | |
| keywords: [ | |
| "funnel visualizer", | |
| "conversion funnel", | |
| "funnel analysis", | |
| "conversion rate optimization", | |
| "sales funnel", | |
| "funnel metrics", | |
| "drop-off analysis", | |
| "customer journey", | |
| "conversion tracking", | |
| "funnel optimization", | |
| ], | |
| openGraph: { | |
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | |
| description: | |
| "Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.", | |
| type: "website", | |
| url: "https://rybbit.io/tools/funnel-visualizer", | |
| }, | |
| twitter: { | |
| card: "summary_large_image", | |
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | |
| description: | |
| "Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.", | |
| }, | |
| alternates: { | |
| canonical: "https://rybbit.io/tools/funnel-visualizer", | |
| }, | |
| }; | |
| export const metadata: Metadata = { | |
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | |
| description: | |
| "Visualize your conversion funnel step-by-step. Input visitor counts at each stage to identify where you're losing customers. Analyze drop-off rates and optimize your funnel for better conversions.", | |
| keywords: [ | |
| "funnel visualizer", | |
| "conversion funnel", | |
| "funnel analysis", | |
| "conversion rate optimization", | |
| "sales funnel", | |
| "funnel metrics", | |
| "drop-off analysis", | |
| "customer journey", | |
| "conversion tracking", | |
| "funnel optimization", | |
| ], | |
| openGraph: { | |
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | |
| description: | |
| "Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.", | |
| type: "website", | |
| url: "https://rybbit.io/tools/funnel-visualizer", | |
| images: [{ | |
| url: "https://rybbit.io/og-images/funnel-visualizer.png", | |
| width: 1200, | |
| height: 630, | |
| alt: "Funnel Visualizer Tool" | |
| }], | |
| }, | |
| twitter: { | |
| card: "summary_large_image", | |
| title: "Free Funnel Visualizer | Marketing Funnel Analysis & Conversion Funnel Tool", | |
| description: | |
| "Visualize your conversion funnel step-by-step. Identify drop-off points and optimize conversion rates.", | |
| images: ["https://rybbit.io/og-images/funnel-visualizer.png"], | |
| }, | |
| alternates: { | |
| canonical: "https://rybbit.io/tools/funnel-visualizer", | |
| }, | |
| }; |
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/funnel-visualizer/page.tsx around lines 9 to 41,
the openGraph and twitter metadata omit image properties which are required for
good social previews (and for summary_large_image on Twitter); add an absolute
URL image (or array) to openGraph.images including optional width, height and
alt text, and add the matching image(s) to twitter.images (or twitter.image)
using the same absolute URL; ensure the image path is reachable (https) and
update both objects accordingly so social cards render with a large preview.
| description: | ||
| "Calculate the monetary value of your website traffic. Understand what each visitor is worth to your business and optimize your marketing spend.", | ||
| type: "website", | ||
| url: "https://rybbit.com/tools/traffic-value-calculator", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify domain consistency: rybbit.com vs rybbit.io
The URLs in the metadata use rybbit.com, but the structured data at line 61 uses rybbit.io, and the marketing-roi-calculator page also uses rybbit.io. Ensure the domain is consistent across all tool pages for proper SEO and canonical URL handling.
Run the following script to check domain usage across tool pages:
Also applies to: 40-40
🏁 Script executed:
#!/bin/bash
# Check domain usage in tool pages
echo "=== Checking rybbit.com usage ==="
rg -n "rybbit\.com" docs/src/app/\(home\)/tools/
echo ""
echo "=== Checking rybbit.io usage ==="
rg -n "rybbit\.io" docs/src/app/\(home\)/tools/Length of output: 8739
Fix domain inconsistency in tool metadata and structured data
The traffic-value-calculator and several other tool pages have inconsistent domains: metadata uses rybbit.com while structured data uses rybbit.io. This occurs in:
- traffic-value-calculator (line 30/40 vs 61)
- utm-builder (line 29/38/49 vs 59)
- bounce-rate-calculator, ctr-calculator, and analytics-detector
Standardize to a single domain across metadata, canonical URLs, and structured data within each tool page for consistent SEO handling.
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/traffic-value-calculator/page.tsx around lines 30
and 61, the metadata URL uses "rybbit.com" while the structured data/canonical
uses "rybbit.io"; standardize both to the same domain (choose one, e.g.,
rybbit.io) by replacing the "rybbit.com" value in the metadata/canonical fields
with "rybbit.io" so metadata, canonical URL, and structured data all match.
…e tools directory - Updated the sitemap function to read tool slugs dynamically from the tools directory, enhancing maintainability and scalability. - Removed the hardcoded list of tool slugs, streamlining the code and reducing the need for manual updates when adding or removing tools.
There was a problem hiding this 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
♻️ Duplicate comments (1)
docs/src/app/sitemap.ts (1)
32-37: Still usingnew Date()forlastModified.As noted in the previous review, setting
lastModifiedto the current date on every sitemap generation tells search engines that all tool pages were just modified, even when their content hasn't changed. This dilutes the SEO signal.Please refer to the previous review comment for suggested solutions using actual file modification times or a stable date.
🧹 Nitpick comments (1)
docs/src/app/sitemap.ts (1)
4-5: Consider using async file system operations for consistency.The function is async, but uses synchronous
readdirSync. While this works in Next.js build contexts, using async operations would be more consistent with the function signature.Apply this diff to use async operations:
-import { readdirSync } from "fs"; +import { readdir } from "fs/promises"; import { join } from "path";Then update the tool discovery:
const toolsPath = join(process.cwd(), "src/app/(home)/tools"); - const toolSlugs = readdirSync(toolsPath, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory() && dirent.name !== "components") - .map(dirent => dirent.name); + let toolSlugs: string[] = []; + try { + const dirents = await readdir(toolsPath, { withFileTypes: true }); + toolSlugs = dirents + .filter(dirent => dirent.isDirectory() && dirent.name !== "components") + .map(dirent => dirent.name); + } catch (error) { + console.warn("Could not read tools directory:", error); + }Also applies to: 27-30
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
docs/src/app/sitemap.ts(4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Client Images (ubuntu-24.04-arm, linux/arm64)
- GitHub Check: Build Client Images (ubuntu-latest, linux/amd64)
| const toolsPath = join(process.cwd(), "src/app/(home)/tools"); | ||
| const toolSlugs = readdirSync(toolsPath, { withFileTypes: true }) | ||
| .filter(dirent => dirent.isDirectory() && dirent.name !== "components") | ||
| .map(dirent => dirent.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for directory operations.
readdirSync will throw an exception if the directory doesn't exist or isn't readable, causing the entire sitemap generation to fail. This could break deployments or builds.
Apply this diff to add error handling:
- const toolsPath = join(process.cwd(), "src/app/(home)/tools");
- const toolSlugs = readdirSync(toolsPath, { withFileTypes: true })
- .filter(dirent => dirent.isDirectory() && dirent.name !== "components")
- .map(dirent => dirent.name);
+ const toolsPath = join(process.cwd(), "src/app/(home)/tools");
+ let toolSlugs: string[] = [];
+ try {
+ toolSlugs = readdirSync(toolsPath, { withFileTypes: true })
+ .filter(dirent => dirent.isDirectory() && dirent.name !== "components")
+ .map(dirent => dirent.name);
+ } catch (error) {
+ console.warn("Could not read tools directory:", error);
+ }📝 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.
| const toolsPath = join(process.cwd(), "src/app/(home)/tools"); | |
| const toolSlugs = readdirSync(toolsPath, { withFileTypes: true }) | |
| .filter(dirent => dirent.isDirectory() && dirent.name !== "components") | |
| .map(dirent => dirent.name); | |
| const toolsPath = join(process.cwd(), "src/app/(home)/tools"); | |
| let toolSlugs: string[] = []; | |
| try { | |
| toolSlugs = readdirSync(toolsPath, { withFileTypes: true }) | |
| .filter(dirent => dirent.isDirectory() && dirent.name !== "components") | |
| .map(dirent => dirent.name); | |
| } catch (error) { | |
| console.warn("Could not read tools directory:", error); | |
| } |
🤖 Prompt for AI Agents
In docs/src/app/sitemap.ts around lines 27 to 30, the call to readdirSync can
throw if the tools directory is missing or unreadable; wrap the directory
operations in a try/catch (or check existence with fs.existsSync/statSync before
calling readdirSync) and fall back to an empty toolSlugs array when the
directory is absent or unreadable, and log a concise warning/error; ensure any
new fs helper used is imported at the top of the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
docs/CLAUDE.md (1)
80-81: Static analysis note: optional URL formatting.Markdownlint flags a bare URL concern on line 81, though no bare URL is visually apparent in the current text. If the linter continues to flag this, consider wrapping the path patterns in backticks (e.g.,
`/docs/:path*.mdx`) to clarify they are code patterns rather than URLs.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.gitignore(1 hunks)CLAUDE.md(1 hunks)docs/CLAUDE.md(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- CLAUDE.md
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
docs/CLAUDE.md
81-81: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Client Images (ubuntu-latest, linux/amd64)
- GitHub Check: Build Client Images (ubuntu-24.04-arm, linux/arm64)
🔇 Additional comments (2)
.gitignore (1)
48-48: Summary inconsistency: verify CLAUDE.md removal.The AI summary states that the CLAUDE.md entry was removed from .gitignore, but the provided code segment shows only line 48 (
.claude/) without showing the removal. Please verify that CLAUDE.md was indeed removed from the gitignore file to align with the PR objective of tracking it in version control.docs/CLAUDE.md (1)
1-81: Well-organized Claude Code guidance documentation.This new file provides comprehensive and well-structured guidance for Claude Code covering project architecture, routing, conventions, and integrations. The information is actionable and appropriately scoped.
- Updated multiple tool pages, including Analytics Detector, Bounce Rate Calculator, CTR Calculator, Funnel Visualizer, Marketing ROI Calculator, and UTM Builder, to use the new ToolPageLayout component for improved consistency and maintainability. - Enhanced educational content and FAQs across these tools for better user engagement and clarity. - Removed redundant imports and streamlined component structures to optimize performance and readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (5)
docs/src/app/sitemap.ts (1)
27-30: Error handling for directory operations is still missing.Although a past review flagged this and marked it as addressed, the current code still lacks error handling. The
readdirSynccall will throw an exception if the directory doesn't exist or isn't readable, causing sitemap generation to fail.Apply this diff to add error handling:
const toolsPath = join(process.cwd(), "src/app/(home)/tools"); - const toolSlugs = readdirSync(toolsPath, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory() && dirent.name !== "components") - .map(dirent => dirent.name); + let toolSlugs: string[] = []; + try { + toolSlugs = readdirSync(toolsPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory() && dirent.name !== "components") + .map(dirent => dirent.name); + } catch (error) { + console.warn("Could not read tools directory:", error); + }docs/src/app/(home)/tools/ctr-calculator/page.tsx (1)
22-38: Confirm intended canonical/organization domains in metadata and structured data.Here the tool URLs (
openGraph.url,alternates.canonical, and WebApplication@id/url) usehttps://rybbit.com/...while the structuredDataauthor.urlpoints tohttps://rybbit.io. That split (marketing site vs app/docs) might be intentional, but it would be good to confirm the canonical host you want search engines to associate with the tool and ensure the pattern is consistent across tool pages (CTR, bounce-rate, etc.).You can quickly audit usage with:
#!/bin/bash # Check tool and org domains used in tool metadata/structuredData rg -n 'https://rybbit\.(com|io)' docs/src/app/(home)/tools --type=ts --type=tsx -C2Also applies to: 45-60
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (1)
6-34: Domain inconsistency remains unresolved across tool pages.This file uses
rybbit.ioin metadata URLs (lines 24, 32) and structured data (line 43), whileutm-builder/page.tsxin this same PR usesrybbit.com(lines 27, 36, 47). This fragmentation will cause SEO issues and break canonical URL expectations.docs/src/app/(home)/tools/utm-builder/page.tsx (1)
9-21: Remove unsupportedkeywordsfield from metadata.As noted in the previous review, the
keywordsfield is not part of Next.jsMetadatatype and will be ignored. This field should be removed.docs/src/app/(home)/tools/funnel-visualizer/page.tsx (1)
22-34: Social media images still missing from metadata.As noted in the previous review, both
openGraphandsummary_large_imagecard type requires an image for proper social previews.
🧹 Nitpick comments (4)
docs/src/app/(home)/tools/MIGRATION_GUIDE.md (2)
185-190: Consider using the new barrel export in examples.Now that
docs/src/app/(home)/tools/components/index.tsre‑exportsToolPageLayout, the “After Migration” example could import from"../components"instead of"../components/ToolPageLayout"to keep docs aligned with the preferred public API.
356-362: Tighten wording in the benefits list.“Exact same structure” is slightly wordy; consider “the same structure” or “a consistent structure” for a cleaner read.
docs/src/app/(home)/tools/analytics-detector/page.tsx (1)
1-7: Preferimport type { Metadata }for type‑only usage.
Metadatais only used as a type annotation, so switching toimport type { Metadata } from "next";would align with the other tool pages and avoid an unnecessary runtime import.If your TS/Next config differs from the defaults, please double‑check that
import typeis supported as expected.docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (1)
21-36: Verify domain split between tool URLs and author URL.Here, the tool’s metadata and WebApplication
urlusehttps://rybbit.com/...while the structuredDataauthor.urluseshttps://rybbit.io. Ifrybbit.iois strictly the app domain andrybbit.comis the canonical marketing/docs domain, this may be fine; otherwise, you may want to align these for SEO clarity and consistency with other tools.Also applies to: 39-58
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
docs/src/app/(home)/tools/MIGRATION_GUIDE.md(1 hunks)docs/src/app/(home)/tools/analytics-detector/page.tsx(1 hunks)docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/components/ToolPageLayout.tsx(1 hunks)docs/src/app/(home)/tools/components/index.ts(1 hunks)docs/src/app/(home)/tools/ctr-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/funnel-visualizer/page.tsx(1 hunks)docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx(1 hunks)docs/src/app/(home)/tools/utm-builder/page.tsx(1 hunks)docs/src/app/sitemap.ts(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (2)
docs/src/app/(home)/tools/components/ToolPageLayout.tsx (1)
ToolPageLayout(33-126)docs/src/app/(home)/tools/funnel-visualizer/FunnelVisualizerForm.tsx (1)
FunnelVisualizerForm(20-230)
docs/src/app/(home)/tools/utm-builder/page.tsx (2)
docs/src/app/(home)/tools/components/ToolPageLayout.tsx (1)
ToolPageLayout(33-126)docs/src/app/(home)/tools/utm-builder/UTMBuilderForm.tsx (1)
UTMBuilderForm(8-233)
docs/src/app/(home)/tools/components/ToolPageLayout.tsx (4)
docs/src/app/(home)/tools/components/index.ts (3)
FAQItem(3-3)ToolPageLayoutProps(3-3)ToolPageLayout(2-2)docs/src/components/Breadcrumbs.tsx (1)
Breadcrumbs(13-48)docs/src/components/ui/accordion.tsx (4)
Accordion(58-58)AccordionItem(58-58)AccordionTrigger(58-58)AccordionContent(58-58)docs/src/components/RelatedTools.tsx (1)
RelatedTools(104-152)
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (2)
docs/src/app/(home)/tools/components/ToolPageLayout.tsx (1)
ToolPageLayout(33-126)docs/src/app/(home)/tools/marketing-roi-calculator/MarketingROIForm.tsx (1)
MarketingROIForm(12-211)
docs/src/app/(home)/tools/ctr-calculator/page.tsx (3)
docs/src/app/(home)/tools/page.tsx (1)
metadata(18-22)docs/src/app/(home)/tools/components/ToolPageLayout.tsx (1)
ToolPageLayout(33-126)docs/src/app/(home)/tools/ctr-calculator/CTRCalculatorForm.tsx (1)
CTRCalculatorForm(15-158)
docs/src/app/(home)/tools/analytics-detector/page.tsx (2)
docs/src/app/(home)/tools/components/ToolPageLayout.tsx (1)
ToolPageLayout(33-126)docs/src/app/(home)/tools/analytics-detector/AnalyticsDetectorForm.tsx (1)
AnalyticsDetectorForm(19-182)
🪛 ast-grep (0.40.0)
docs/src/app/(home)/tools/components/ToolPageLayout.tsx
[warning] 50-50: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
docs/src/app/(home)/tools/components/ToolPageLayout.tsx
[error] 51-51: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🪛 LanguageTool
docs/src/app/(home)/tools/MIGRATION_GUIDE.md
[style] ~358-~358: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: .... Consistency: All tools follow the exact same structure 2. Maintainability: Updat...
(EN_WORDINESS_PREMIUM_EXACT_SAME)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Client Images (ubuntu-24.04-arm, linux/arm64)
- GitHub Check: Build Client Images (ubuntu-latest, linux/amd64)
🔇 Additional comments (15)
docs/src/app/sitemap.ts (3)
4-5: LGTM!The imports are appropriate for the new directory-reading functionality.
59-64: LGTM!The new
/toolsstatic page entry is well-configured with appropriate priority and change frequency. Usingnew Date()for the tools landing page is acceptable since it serves as an index.
73-73: LGTM!The return statement correctly includes the new
toolPagesin the sitemap with appropriate ordering.docs/src/app/(home)/tools/components/index.ts (1)
1-3: Barrel exports look good.Clean, minimal barrel that exposes the intended components and types; this will help keep imports consistent across tool pages.
docs/src/app/(home)/tools/ctr-calculator/page.tsx (1)
1-4: Imports and layout usage look solid.Using
import type { Metadata }and wiringToolPageLayoutwithtoolSlug, content blocks, FAQs, andstructuredDatamatches the migration guide and the layout’s props contract.docs/src/app/(home)/tools/analytics-detector/page.tsx (1)
288-303: ToolPageLayout integration is consistent and complete.All required props (slug, title, description, toolComponent, educationalContent, faqs, relatedToolsCategory, CTA fields, structuredData) are provided and match the documented ToolPageLayout contract.
docs/src/app/(home)/tools/bounce-rate-calculator/page.tsx (1)
355-370: Bounce Rate Calculator page wiring looks correct.ToolPageLayout is configured with the expected slug, content blocks, FAQ array, analytics category, CTA, and structuredData, matching the new tool-page pattern.
docs/src/app/(home)/tools/marketing-roi-calculator/page.tsx (2)
106-301: Educational content is comprehensive and well-structured.The educationalContent block provides valuable information about ROI concepts, formulas, benchmarks, best practices, and optimization strategies. The structure is clear and accessible.
340-357: ToolPageLayout integration looks correct.The default export properly delegates rendering to ToolPageLayout with all required props, aligning with the standardized tool page pattern across the PR.
docs/src/app/(home)/tools/utm-builder/page.tsx (1)
108-253: Educational content is thorough and helpful.The UTM parameter explanations, naming conventions, use cases, and common mistakes are well-documented and provide practical guidance for users.
docs/src/app/(home)/tools/funnel-visualizer/page.tsx (2)
110-235: Educational content provides excellent funnel analysis guidance.The content covers funnel concepts, stages, drop-off analysis methodology, and optimization strategies comprehensively. Well-organized and actionable.
275-292: ToolPageLayout integration is correct.The component properly uses ToolPageLayout with all necessary props, matching the pattern established across other tool pages.
docs/src/app/(home)/tools/components/ToolPageLayout.tsx (3)
7-31: Interface definitions are clear and well-typed.The
FAQItemandToolPageLayoutPropsinterfaces provide a clean contract for the layout component, with appropriate required/optional fields and ReactNode flexibility for content.
33-126: Component structure follows a logical, accessible layout.The six-section layout (breadcrumbs, header, tool, educational content, FAQ, related tools, CTA) provides consistent structure across tool pages. The accordion implementation for FAQs is accessible, and the prose classes on educational content ensure good typography.
50-52: All structuredData props are safely statically-defined.Verification confirms that every instance of
structuredDatapassed toToolPageLayoutacross all tool pages (utm-builder, ctr-calculator, bounce-rate-calculator, traffic-value-calculator, seo-title-generator, privacy-policy-builder, sample-size-calculator, meta-description-generator, funnel-visualizer, analytics-detector, og-tag-generator, and marketing-roi-calculator) uses hardcoded, module-level constant definitions following the patternconst structuredData = { "@context": "https://schema.org", "@graph": [...] }. No user input, dynamic data, or external API responses are being passed todangerouslySetInnerHTML. The use ofJSON.stringify()before injection is appropriate for this pattern.
| description: | ||
| "Generate UTM campaign URLs instantly for accurate marketing tracking in Google Analytics and other analytics platforms.", | ||
| type: "website", | ||
| url: "https://rybbit.com/tools/utm-builder", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Domain inconsistency: this page uses rybbit.com while others use rybbit.io.
Lines 27, 36, and 47 use rybbit.com, but marketing-roi-calculator/page.tsx and funnel-visualizer/page.tsx in this PR use rybbit.io. Choose one canonical domain and apply it consistently across all tool metadata and structured data.
Also applies to: 36-36, 47-47
🤖 Prompt for AI Agents
In docs/src/app/(home)/tools/utm-builder/page.tsx around lines 27, 36 and 47,
the page uses the domain "rybbit.com" while other tool pages in this PR use
"rybbit.io"; update these three occurrences to the canonical domain (choose and
use "rybbit.io") so the metadata and structured data are consistent across all
tool pages, and verify no other instances remain in this file.
…al content - Updated multiple tool pages, including Bounce Rate Calculator, Marketing ROI Calculator, and others, to utilize the ToolPageLayout component for improved consistency and maintainability. - Enhanced educational content and FAQs across these tools for better user engagement and clarity. - Removed redundant imports and streamlined component structures to optimize performance and readability.
- Modified multiple tool pages, including Bounce Rate Calculator, CTR Calculator, Funnel Visualizer, Marketing ROI Calculator, Page Speed Calculator, Traffic Value Calculator, and UTM Builder, to update URLs in metadata and structured data for consistency with the new domain. - Enhanced educational content for clarity and improved readability across these tools. - Ensured all references to the old domain are replaced to maintain functionality and SEO integrity.
…mprove SEO practices - Updated the metadata for various tools, including CLAUDE, Analytics Detector, Bounce Rate Calculator, CTR Calculator, Funnel Visualizer, Marketing ROI Calculator, Meta Description Generator, Open Graph Tag Generator, Page Speed Calculator, Privacy Policy Builder, Sample Size Calculator, SEO Title Generator, and Traffic Value Calculator, by removing the keywords array. - Enhanced educational content for clarity and improved readability across these tools. - Ensured consistency in metadata structure to align with best SEO practices.
…cture
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.