diff --git a/docs/content/blog/_meta.js b/docs/content/blog/_meta.js index 940707ae7..8b07242a2 100644 --- a/docs/content/blog/_meta.js +++ b/docs/content/blog/_meta.js @@ -5,4 +5,6 @@ export default { "Why did my website traffic drop?", "onyx-ai-knowledge-platform": "How We Use Onyx to Power Knowledge Management at Rybbit", + "bounce-rate": + "Understanding Bounce Rate and How to Improve It", }; \ No newline at end of file diff --git a/docs/content/docs/product-analytics/bounce-rate.mdx b/docs/content/blog/bounce-rate.mdx similarity index 99% rename from docs/content/docs/product-analytics/bounce-rate.mdx rename to docs/content/blog/bounce-rate.mdx index bb0950081..534d8a24d 100644 --- a/docs/content/docs/product-analytics/bounce-rate.mdx +++ b/docs/content/blog/bounce-rate.mdx @@ -1,6 +1,8 @@ --- title: How to Calculate Bounce Rate with Rybbit Analytics description: How to Calculate Bounce Rate with Rybbit Analytics +date: 2025-11-16 +tags: ['bounce rate', 'stats', 'analytics'] --- # How to Calculate Bounce Rate with Rybbit Analytics diff --git a/docs/content/docs/product-analytics/meta.json b/docs/content/docs/product-analytics/meta.json index 43e6f43d4..29bff5353 100644 --- a/docs/content/docs/product-analytics/meta.json +++ b/docs/content/docs/product-analytics/meta.json @@ -1,4 +1,4 @@ { "title": "Product Analytics", - "pages": ["bounce-rate", "replay-session"] + "pages": ["replay-session"] } diff --git a/docs/content/docs/web-analytics/goals-tab.mdx b/docs/content/docs/web-analytics/goals-tab.mdx index 79ddb51d9..8fc34c348 100644 --- a/docs/content/docs/web-analytics/goals-tab.mdx +++ b/docs/content/docs/web-analytics/goals-tab.mdx @@ -5,18 +5,14 @@ description: How to use the Goals dashboard in Rybbit Analytics ## The Goals Tab -Goals let you track specific outcomes you care about. Did users sign up? Did they complete checkout? Did they view key pages? The Goals tab helps you measure success. +![Rybbit Goals Dashboard](/blog/rybbit_goals_dashboard.png) -![Screenshot of Goals tab overview] -*Image: Goals tab interface* +Goals let you track specific outcomes you care about. Did users sign up? Did they complete checkout? Did they view key pages? The Goals tab helps you measure success. ### Creating Your First Goal Click "Create Goal" to open the goal setup interface. -![Screenshot of goal creation form] -*Image: New goal setup screen* - You'll need to provide: **Goal Name**: Something descriptive like "Newsletter Signup" or "Purchase Complete" @@ -29,9 +25,6 @@ You'll need to provide: **Value** (optional): Assign a monetary value to conversions -![Screenshot showing goal type selection] -*Image: Choosing goal type* - ### Page Path Goals Page path goals trigger when users visit specific URLs. Common examples: @@ -41,9 +34,6 @@ Page path goals trigger when users visit specific URLs. Common examples: - Onboarding completion pages - Download confirmation pages -![Screenshot of page path goal configuration] -*Image: Setting up a page path goal* - Enter the URL path exactly as it appears in your analytics. Use wildcards for dynamic URLs: - `/thank-you` matches exactly `/thank-you` - `/thank-you*` matches `/thank-you`, `/thank-you?ref=email`, etc. @@ -53,9 +43,6 @@ Enter the URL path exactly as it appears in your analytics. Use wildcards for dy Event goals trigger when custom events fire. You need to have custom event tracking implemented first. -![Screenshot of event goal configuration] -*Image: Setting up an event-based goal* - Common event goals: - Button clicks ("signup_clicked") - Video completions ("video_complete") @@ -68,9 +55,6 @@ Events offer more flexibility than page goals. You can track actions that don't After creating goals, the Goals tab displays your conversion metrics. -![Screenshot of goals dashboard with multiple goals] -*Image: Overview of all active goals* - For each goal, you'll see: - **Conversion Count**: How many times this goal converted - **Conversion Rate**: Percentage of sessions that converted @@ -81,9 +65,6 @@ For each goal, you'll see: Click any goal to see detailed funnel analysis. This shows the path users take before converting. -![Screenshot of funnel analysis for a goal] -*Image: Conversion funnel visualization* - The funnel reveals: - Top entry pages for converting sessions - Most common paths to conversion @@ -100,9 +81,6 @@ This analysis helps you: See which channels drive the most conversions. The Goals tab breaks down conversions by source. -![Screenshot of goal conversions by source] -*Image: Conversion rates by traffic channel* - Compare conversion rates across: - Organic search - Social media @@ -116,9 +94,6 @@ Maybe organic search converts at 5% while social converts at 1%. This tells you Device type affects conversion rates. Mobile users often convert less than desktop users, but that varies by industry. -![Screenshot of goals by device type] -*Image: Conversion rates across devices* - If mobile conversion rates lag significantly, you need mobile optimization. Focus on: - Simplifying mobile checkout flows - Reducing form fields @@ -129,9 +104,6 @@ If mobile conversion rates lag significantly, you need mobile optimization. Focu Assigning values to goals lets you track revenue or value generated through your site. -![Screenshot of goal value configuration] -*Image: Adding monetary value to a goal* - Set values based on: - Average order value (for purchases) - Estimated lead value (for B2B sites) @@ -159,18 +131,12 @@ Most sites should track several goals. Create goals for: - Account creation - Premium upgrades -![Screenshot showing multiple active goals] -*Image: Dashboard with various goal types* - Tracking multiple goals shows you the complete picture. Maybe signups are up but purchases are down. Or downloads increase but signups decline. These patterns inform strategy. ### Time to Conversion For each goal, see how long it takes users to convert after their first visit. -![Screenshot of time to conversion chart] -*Image: Conversion timing distribution* - Most conversions happen: - Immediately (same session) - Within 24 hours diff --git a/docs/content/updates/meta.js b/docs/content/updates/meta.js new file mode 100644 index 000000000..b6108d131 --- /dev/null +++ b/docs/content/updates/meta.js @@ -0,0 +1,61 @@ +export default { + "title": "Rybbit Releases", + "description": "Release notes and changelogs for Rybbit - open-source privacy-friendly analytics", + "pages": { + "v2.0.0": { + "title": "v2.0.0", + "description": "Major update with Map/Globe rework, Journeys page redesign, and server-side script configs", + "date": "2025-10-24" + }, + "v1.6.1": { + "title": "v1.6.1", + "description": "Minor bug fixes and improvements", + "date": "2025-10" + }, + "v1.6.0": { + "title": "v1.6.0", + "description": "Added tracking outbound links, IP exclusion, custom error tracking, and UI improvements", + "date": "2025-09" + }, + "v1.5.1": { + "title": "v1.5.1", + "description": "Minor bug fixes and improvements", + "date": "2025-08" + }, + "v1.5.0": { + "title": "v1.5.0", + "description": "Added organization members support, multiple organizations, session replay improvements, and bug fixes", + "date": "2025-07" + }, + "v1.4.1": { + "title": "v1.4.1", + "description": "Minor bug fixes and improvements", + "date": "2025-07" + }, + "v1.4.0": { + "title": "v1.4.0", + "description": "Major update introducing Session Replay and Error Tracking features", + "date": "2025-06-29" + }, + "v1.3.0": { + "title": "v1.3.0", + "description": "Added API keys for tracking events and pageviews, with localhost tracking support", + "date": "2025-06" + }, + "v1.2.0": { + "title": "v1.2.0", + "description": "Added more integrations, API improvements, and blog support", + "date": "2025-05" + }, + "v1.1.0": { + "title": "v1.1.0", + "description": "Added data attribute event tracking, opt-out extension support, and subdomain tracking", + "date": "2025-04" + }, + "v1.0.0": { + "title": "v1.0.0", + "description": "First major release with Browser/OS version filtering and breaking changes for reverse proxy configs", + "date": "2025-03" + } + } +}; \ No newline at end of file diff --git a/docs/content/updates/v.1.4.1.mdx b/docs/content/updates/v.1.4.1.mdx new file mode 100644 index 000000000..ce5e5f1c7 --- /dev/null +++ b/docs/content/updates/v.1.4.1.mdx @@ -0,0 +1,9 @@ +--- +title: "v1.4.1" +description: "Minor bug fixes and improvements" +date: "2025-07-01" +--- + +# v1.4.1 + +Minor release with bug fixes and improvements. \ No newline at end of file diff --git a/docs/content/updates/v1.0.0.mdx b/docs/content/updates/v1.0.0.mdx new file mode 100644 index 000000000..e7d75dbd9 --- /dev/null +++ b/docs/content/updates/v1.0.0.mdx @@ -0,0 +1,41 @@ +--- +title: "v1.0.0" +description: "First major release with Browser/OS version filtering and breaking changes for reverse proxy configs" +date: "2025-03-01" +--- + +# v1.0.0 + +## Migration + +There are breaking changes if you used a custom reverse proxy to host Rybbit - We have removed the `/api` rewrite and you will need to modify your reverse proxy config + +## Key Features + +### Browser Versions + +Filter analytics data by specific browser versions to understand user distribution across different browser releases. + +### Operation System Versions + +Filter analytics data by operating system versions to gain insights into your user's platform preferences. + +## What's Changed + +- Free by [@goldflag](https://github.com/goldflag) in [#313](https://github.com/rybbit-io/rybbit/pull/313) +- fix og by [@goldflag](https://github.com/goldflag) in [#320](https://github.com/rybbit-io/rybbit/pull/320) +- Update self-hosting-advanced.mdx by [@smileBeda](https://github.com/smileBeda) in [#323](https://github.com/rybbit-io/rybbit/pull/323) +- fix: healthcheck log level silent by [@nktnet1](https://github.com/nktnet1) in [#317](https://github.com/rybbit-io/rybbit/pull/317) +- feat(filters): add support for browser and OS version filters by [@goldflag](https://github.com/goldflag) in [#326](https://github.com/rybbit-io/rybbit/pull/326) +- blog by [@goldflag](https://github.com/goldflag) in [#269](https://github.com/rybbit-io/rybbit/pull/269) +- doc: add optional section on rewriting the tracking script. by [@rexwangcc](https://github.com/rexwangcc) in [#328](https://github.com/rybbit-io/rybbit/pull/328) +- feat(api): implement organization-specific site retrieval and subscri… by [@goldflag](https://github.com/goldflag) in [#333](https://github.com/rybbit-io/rybbit/pull/333) +- docs: add mention of cloudflare specific issues by [@mezotv](https://github.com/mezotv) in [#337](https://github.com/rybbit-io/rybbit/pull/337) +- remove reverse proxy rewrite by [@goldflag](https://github.com/goldflag) in [#340](https://github.com/rybbit-io/rybbit/pull/340) + +## New Contributors + +- [@rexwangcc](https://github.com/rexwangcc) made their first contribution in [#328](https://github.com/rybbit-io/rybbit/pull/328) +- [@mezotv](https://github.com/mezotv) made their first contribution in [#337](https://github.com/rybbit-io/rybbit/pull/337) + +**Full Changelog**: [v0.4.2...v1.0.0](https://github.com/rybbit-io/rybbit/compare/v0.4.2...v1.0.0) \ No newline at end of file diff --git a/docs/content/updates/v1.1.0.mdx b/docs/content/updates/v1.1.0.mdx new file mode 100644 index 000000000..2dce04f5d --- /dev/null +++ b/docs/content/updates/v1.1.0.mdx @@ -0,0 +1,32 @@ +--- +title: "v1.1.0" +description: "Added data attribute event tracking, opt-out extension support, and subdomain tracking" +date: "2025-04-01" +--- + +# v1.1.0 + +## Key Features + +1. Track events using [data attributes](https://www.rybbit.io/docs/track-events#using-data-attributes) +2. Add support for Rybbit [opt-out extension](https://chromewebstore.google.com/detail/rybbit-opt-out/gofnfdfidbkpellionhpmkejgaajieoo). Thank you [@davidfiala](https://github.com/davidfiala) +3. Added support for tracking subdomains within the same site + +## What's Changed + +- Debug pg by [@goldflag](https://github.com/goldflag) in [#352](https://github.com/rybbit-io/rybbit/pull/352) +- Apple debug by [@goldflag](https://github.com/goldflag) in [#354](https://github.com/rybbit-io/rybbit/pull/354) +- fix(healthcheck): v1.0.0 now requires /api prefix in base path by [@nktnet1](https://github.com/nktnet1) in [#355](https://github.com/rybbit-io/rybbit/pull/355) +- Update README.md by [@smileBeda](https://github.com/smileBeda) in [#236](https://github.com/rybbit-io/rybbit/pull/236) +- Create CONTRIBUTE.md by [@smileBeda](https://github.com/smileBeda) in [#235](https://github.com/rybbit-io/rybbit/pull/235) +- Create SECURITY.md by [@smileBeda](https://github.com/smileBeda) in [#234](https://github.com/rybbit-io/rybbit/pull/234) +- support rybbit opt-out extension and add explicit terser packing script to package.json by [@davidfiala](https://github.com/davidfiala) in [#363](https://github.com/rybbit-io/rybbit/pull/363) +- Comparison by [@goldflag](https://github.com/goldflag) in [#369](https://github.com/rybbit-io/rybbit/pull/369) +- Enhance event tracking capabilities and update documentation by [@goldflag](https://github.com/goldflag) in [#374](https://github.com/rybbit-io/rybbit/pull/374) +- Enhance domain normalization and tracking validation by [@goldflag](https://github.com/goldflag) in [#376](https://github.com/rybbit-io/rybbit/pull/376) + +## New Contributors + +- [@davidfiala](https://github.com/davidfiala) made their first contribution in [#363](https://github.com/rybbit-io/rybbit/pull/363) + +**Full Changelog**: [v1.0.0...v1.1.0](https://github.com/rybbit-io/rybbit/compare/v1.0.0...v1.1.0) \ No newline at end of file diff --git a/docs/content/updates/v1.2.0.mdx b/docs/content/updates/v1.2.0.mdx new file mode 100644 index 000000000..3d026854e --- /dev/null +++ b/docs/content/updates/v1.2.0.mdx @@ -0,0 +1,30 @@ +--- +title: "v1.2.0" +description: "Added more integrations, API improvements, and blog support" +date: "2025-05-01" +--- + +# v1.2.0 + +## What's Changed + +- Add more integrations by [@goldflag](https://github.com/goldflag) in [#377](https://github.com/rybbit-io/rybbit/pull/377) +- Fix docs by [@goldflag](https://github.com/goldflag) in [#379](https://github.com/rybbit-io/rybbit/pull/379) +- shared package by [@goldflag](https://github.com/goldflag) in [#380](https://github.com/rybbit-io/rybbit/pull/380) +- Enhance API utility functions to use axios for HTTP requests by [@goldflag](https://github.com/goldflag) in [#387](https://github.com/rybbit-io/rybbit/pull/387) +- fix optout- original commit messed up the && vs || operator, meaning v1.0.0 and v1.1.0 require both to opt out. apologies this was missed by [@davidfiala](https://github.com/davidfiala) in [#389](https://github.com/rybbit-io/rybbit/pull/389) +- adding blog by [@JMeng1](https://github.com/JMeng1) in [#385](https://github.com/rybbit-io/rybbit/pull/385) +- Blog post - adding pictures by [@JMeng1](https://github.com/JMeng1) in [#393](https://github.com/rybbit-io/rybbit/pull/393) +- Remove deprecated analytics hooks and refactor API structure for funn… by [@goldflag](https://github.com/goldflag) in [#394](https://github.com/rybbit-io/rybbit/pull/394) +- Refactor analytics API endpoints to utilize `FilterParams` for improv… by [@goldflag](https://github.com/goldflag) in [#396](https://github.com/rybbit-io/rybbit/pull/396) +- Refactor analytics time mode handling to support past minutes by [@goldflag](https://github.com/goldflag) in [#400](https://github.com/rybbit-io/rybbit/pull/400) +- Refactor package management and API functions for improved clarity an… by [@goldflag](https://github.com/goldflag) in [#401](https://github.com/rybbit-io/rybbit/pull/401) +- Add SVG logos to assets — resolves [#406](https://github.com/rybbit-io/rybbit/issues/406) by [@poliroid](https://github.com/poliroid) in [#408](https://github.com/rybbit-io/rybbit/pull/408) +- build script by [@goldflag](https://github.com/goldflag) in [#410](https://github.com/rybbit-io/rybbit/pull/410) + +## New Contributors + +- [@JMeng1](https://github.com/JMeng1) made their first contribution in [#385](https://github.com/rybbit-io/rybbit/pull/385) +- [@poliroid](https://github.com/poliroid) made their first contribution in [#408](https://github.com/rybbit-io/rybbit/pull/408) + +**Full Changelog**: [v1.1.0...v1.2.0](https://github.com/rybbit-io/rybbit/compare/v1.1.0...v1.2.0) \ No newline at end of file diff --git a/docs/content/updates/v1.3.0.mdx b/docs/content/updates/v1.3.0.mdx new file mode 100644 index 000000000..c3ec2be13 --- /dev/null +++ b/docs/content/updates/v1.3.0.mdx @@ -0,0 +1,15 @@ +--- +title: "v1.3.0" +description: "Added API keys for tracking events and pageviews, with localhost tracking support" +date: "2025-06-01" +--- + +# v1.3.0 + +## Key Features + +- Added API keys for tracking events/pageviews via [http endpoint](https://www.rybbit.io/docs/api) +- API keys can also be used for tracking events on [localhost](https://www.rybbit.io/docs/localhost-tracking) +- Bug fixes + +**Full Changelog**: [v1.2.0...v1.3.0](https://github.com/rybbit-io/rybbit/compare/v1.2.0...v1.3.0) \ No newline at end of file diff --git a/docs/content/updates/v1.4.0.mdx b/docs/content/updates/v1.4.0.mdx new file mode 100644 index 000000000..8c05a7628 --- /dev/null +++ b/docs/content/updates/v1.4.0.mdx @@ -0,0 +1,30 @@ +--- +title: "v1.4.0" +description: "Major update introducing Session Replay and Error Tracking features" +date: "2025-06-29" +--- + +# v1.4.0 + +**Note**: You may have to run `git stash` before running the `./update` script since I made some changes to it. + +## Key Features + +### Session Replay + +Watch real user sessions to understand their behavior and identify pain points. + +### Error Tracking + +Track and monitor errors that occur on your website, with detailed information about each error. + +## Commit Log + +- Add error tracking feature and update documentation by [@goldflag](https://github.com/goldflag) in [#418](https://github.com/rybbit-io/rybbit/pull/418) +- Implement session replay feature and enhance analytics script by [@goldflag](https://github.com/goldflag) in [#424](https://github.com/rybbit-io/rybbit/pull/424) +- Test by [@goldflag](https://github.com/goldflag) in [#429](https://github.com/rybbit-io/rybbit/pull/429) +- Update viewport dimensions to use screen size for tracking data by [@goldflag](https://github.com/goldflag) in [#431](https://github.com/rybbit-io/rybbit/pull/431) +- Refactor Docker configuration and update Caddyfile for Rybbit services by [@goldflag](https://github.com/goldflag) in [#411](https://github.com/rybbit-io/rybbit/pull/411) +- Fix rrweb by [@goldflag](https://github.com/goldflag) in [#434](https://github.com/rybbit-io/rybbit/pull/434) + +**Full Changelog**: [v1.3.0...v1.4.0](https://github.com/rybbit-io/rybbit/compare/v1.3.0...v1.4.0) \ No newline at end of file diff --git a/docs/content/updates/v1.5.0.mdx b/docs/content/updates/v1.5.0.mdx new file mode 100644 index 000000000..8df5a813e --- /dev/null +++ b/docs/content/updates/v1.5.0.mdx @@ -0,0 +1,34 @@ +--- +title: "v1.5.0" +description: "Added organization members support, multiple organizations, session replay improvements, and bug fixes" +date: "2025-07-01" +--- + +# v1.5.0 + +## Key Features + +- Add support for adding organization members on self-hosted instances +- Add support for multiple organizations +- Session replay performance improvements +- Bug fixes + +## Commit Log + +- Fix `update.sh` "Please commit your changes or stash them before you merge. by [@Mr-Technician](https://github.com/Mr-Technician) in [#438](https://github.com/rybbit-io/rybbit/pull/438) +- producthunt button by [@goldflag](https://github.com/goldflag) in [#440](https://github.com/rybbit-io/rybbit/pull/440) +- Landing refactor by [@goldflag](https://github.com/goldflag) in [#447](https://github.com/rybbit-io/rybbit/pull/447) +- Update session replay configuration and improve event handling by [@goldflag](https://github.com/goldflag) in [#455](https://github.com/rybbit-io/rybbit/pull/455) +- R2 by [@goldflag](https://github.com/goldflag) in [#457](https://github.com/rybbit-io/rybbit/pull/457) +- bump better auth by [@goldflag](https://github.com/goldflag) in [#458](https://github.com/rybbit-io/rybbit/pull/458) +- improve r2 perf by [@goldflag](https://github.com/goldflag) in [#459](https://github.com/rybbit-io/rybbit/pull/459) +- Replace tracking with track to fix [#474](https://github.com/rybbit-io/rybbit/issues/474) by [@didyouexpectthat](https://github.com/didyouexpectthat) in [#475](https://github.com/rybbit-io/rybbit/pull/475) +- Enhance session replay functionality with API key support by [@goldflag](https://github.com/goldflag) in [#483](https://github.com/rybbit-io/rybbit/pull/483) +- Refactor AccountInner and Organization components for improved readab… by [@goldflag](https://github.com/goldflag) in [#484](https://github.com/rybbit-io/rybbit/pull/484) + +## New Contributors + +- [@Mr-Technician](https://github.com/Mr-Technician) made their first contribution in [#438](https://github.com/rybbit-io/rybbit/pull/438) +- [@didyouexpectthat](https://github.com/didyouexpectthat) made their first contribution in [#475](https://github.com/rybbit-io/rybbit/pull/475) + +**Full Changelog**: [v1.4.1...v1.5.0](https://github.com/rybbit-io/rybbit/compare/v1.4.1...v1.5.0) \ No newline at end of file diff --git a/docs/content/updates/v1.5.1.mdx b/docs/content/updates/v1.5.1.mdx new file mode 100644 index 000000000..5e5dfd373 --- /dev/null +++ b/docs/content/updates/v1.5.1.mdx @@ -0,0 +1,9 @@ +--- +title: "v1.5.1" +description: "Minor bug fixes and improvements" +date: "2025-08-01" +--- + +# v1.5.1 + +Minor release with bug fixes and improvements. \ No newline at end of file diff --git a/docs/content/updates/v1.6.0.mdx b/docs/content/updates/v1.6.0.mdx new file mode 100644 index 000000000..975689eb3 --- /dev/null +++ b/docs/content/updates/v1.6.0.mdx @@ -0,0 +1,46 @@ +--- +title: "v1.6.0" +description: "Added tracking outbound links, IP exclusion, custom error tracking, and UI improvements" +date: "2025-09-01" +--- + +# v1.6.0 + +## Key Features + +- Add tracking outbound links +- Removed header and moved everything into the sidebar +- Add IP exclusion +- Add ability to track custom errors +- Moved Clickhouse configs inside docker-compose file +- A bunch of bug fixes + +## Commit Log + +- Use the total event count to calculate the percentage of each event type by [@rockinrimmer](https://github.com/rockinrimmer) in [#490](https://github.com/rybbit-io/rybbit/pull/490) +- Signup refactor by [@goldflag](https://github.com/goldflag) in [#491](https://github.com/rybbit-io/rybbit/pull/491) +- Update AdminPage and Organizations components for improved tab manage… by [@goldflag](https://github.com/goldflag) in [#486](https://github.com/rybbit-io/rybbit/pull/486) +- Handle errors in the session timeline. These were previously treated … by [@rockinrimmer](https://github.com/rockinrimmer) in [#505](https://github.com/rybbit-io/rybbit/pull/505) +- Uptime by [@goldflag](https://github.com/goldflag) in [#496](https://github.com/rybbit-io/rybbit/pull/496) +- Add a "Source Information" section to the "Session Info" tab when vie… by [@rockinrimmer](https://github.com/rockinrimmer) in [#504](https://github.com/rybbit-io/rybbit/pull/504) +- Custom Event Fixes & Tweaks by [@rockinrimmer](https://github.com/rockinrimmer) in [#501](https://github.com/rybbit-io/rybbit/pull/501) +- Allow subdomains on origin validation by [@stijnie2210](https://github.com/stijnie2210) in [#493](https://github.com/rybbit-io/rybbit/pull/493) +- improve logging by [@goldflag](https://github.com/goldflag) in [#525](https://github.com/rybbit-io/rybbit/pull/525) +- Add Axiom logging support and update environment configuration by [@goldflag](https://github.com/goldflag) in [#526](https://github.com/rybbit-io/rybbit/pull/526) +- Exclude ips by [@goldflag](https://github.com/goldflag) in [#527](https://github.com/rybbit-io/rybbit/pull/527) +- Add ability to track custom errors by [@SmartArray](https://github.com/SmartArray) in [#529](https://github.com/rybbit-io/rybbit/pull/529) +- Track outbound events by [@stijnie2210](https://github.com/stijnie2210) in [#536](https://github.com/rybbit-io/rybbit/pull/536) +- update outbound link ui by [@goldflag](https://github.com/goldflag) in [#542](https://github.com/rybbit-io/rybbit/pull/542) +- Refactor ClickHouse configuration in Docker Compose files by [@goldflag](https://github.com/goldflag) in [#543](https://github.com/rybbit-io/rybbit/pull/543) +- Fuma by [@goldflag](https://github.com/goldflag) in [#532](https://github.com/rybbit-io/rybbit/pull/532) +- Fix zstd by [@goldflag](https://github.com/goldflag) in [#547](https://github.com/rybbit-io/rybbit/pull/547) +- Try to fix integrations by [@goldflag](https://github.com/goldflag) in [#556](https://github.com/rybbit-io/rybbit/pull/556) +- bump version by [@goldflag](https://github.com/goldflag) in [#557](https://github.com/rybbit-io/rybbit/pull/557) + +## New Contributors + +- [@rockinrimmer](https://github.com/rockinrimmer) made their first contribution in [#490](https://github.com/rybbit-io/rybbit/pull/490) +- [@stijnie2210](https://github.com/stijnie2210) made their first contribution in [#493](https://github.com/rybbit-io/rybbit/pull/493) +- [@SmartArray](https://github.com/SmartArray) made their first contribution in [#529](https://github.com/rybbit-io/rybbit/pull/529) + +**Full Changelog**: [v1.5.1...v1.6.0](https://github.com/rybbit-io/rybbit/compare/v1.5.1...v1.6.0) \ No newline at end of file diff --git a/docs/content/updates/v1.6.1.mdx b/docs/content/updates/v1.6.1.mdx new file mode 100644 index 000000000..9dbb3b207 --- /dev/null +++ b/docs/content/updates/v1.6.1.mdx @@ -0,0 +1,9 @@ +--- +title: "v1.6.1" +description: "Minor bug fixes and improvements" +date: "2025-10-01" +--- + +# v1.6.1 + +Minor release with bug fixes and improvements. \ No newline at end of file diff --git a/docs/content/updates/v2.0.0.mdx b/docs/content/updates/v2.0.0.mdx new file mode 100644 index 000000000..7118f692d --- /dev/null +++ b/docs/content/updates/v2.0.0.mdx @@ -0,0 +1,59 @@ +--- +title: "v2.0.0" +description: "Major update with Map/Globe rework, Journeys page redesign, and server-side script configs" +date: "2025-10-24" +--- + +# v2.0.0 + +We haven't had a release for 2 months, but this is a big one! It's the biggest update in the short history of Rybbit, and includes a minor breaking change (you just need to toggle some switches in the UI if affected) so I've decided to mark it as 2.0. + +## Map/Globe Rework + +The Map and Realtime pages have been replaced with the Globe page. There isn't a straight up 1-to-1 replacement of the Realtime page yet, but I might add it in a future release. + +Leaflet has been replaced with Openlayers. I added a 3D mode using Mapbox, but I have left this feature out of the open source release because Mapbox requires a credit card to obtain an API key (even if you stay within the free tier). To be clear, the 2D map is in the self-hosted release. + +### New Globe Features + +- **Timeline view** where you can see and replay visits over time +- **Coordinates view** where you can see the approximate location of sessions +- Country and subdivision layers are largely the same + +## Journeys Page Rework + +- General UI redesign +- Added step path filter +- Hover interactions on nodes and connections now make more sense + +## Goal and Funnel Improvements + +- Added ability to clone goals and funnels +- Added search filter to goals and funnels + +## User Identification Improvements + +- Changed user avatar +- Now displays a random name for each user instead of the raw ID (ID is still available) + +## Script Configs are now Server-side + +**This is technically the breaking change of this 2.x release** + +I didn't like how some of the configs were living on the tracking script and some were in the site settings UI, so I moved almost all of them to site settings. You no longer have to redeploy your script every time you want to update the tracking config. + +For anyone currently setting fields on the tracking script, just head over to your site settings and toggle stuff on. + +## Filter Improvements + +- Fixed the "Event Name" filter. This was always broken in the past +- In the main tab you can now filter by events by clicking on them +- Added more filters to more pages, most notably the Hostname filter + +## Optional IP Address Collection + +You can now toggle visitor IP addresses. IP address collection is off by default because it is very privacy unfriendly, but it can be useful for debugging or blocking bots/spammers. + +## Channel Information + +Added channel information to the Sessions, Users, and Globe pages \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json index 2ef0ab2fc..d38dfa192 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -27,6 +27,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-tweet": "^3.2.2", + "resend": "^6.4.2", "tailwind-merge": "^3.3.1", "zod": "^4.1.12" }, @@ -109,6 +110,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1856,17 +1858,6 @@ "node": ">=12.4.0" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@orama/orama": { "version": "3.1.16", "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-3.1.16.tgz", @@ -2746,6 +2737,12 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -3386,6 +3383,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3396,6 +3394,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3459,6 +3458,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -3994,6 +3994,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4374,6 +4375,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -4540,6 +4542,7 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", @@ -4732,6 +4735,7 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -5132,6 +5136,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -5655,6 +5660,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -5757,6 +5768,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5942,6 +5954,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6341,6 +6354,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -6453,6 +6472,7 @@ "resolved": "https://registry.npmjs.org/fumadocs-core/-/fumadocs-core-16.0.3.tgz", "integrity": "sha512-3kr1u0V0iHgIcQek+iRQKvJdbHp+Th8PAO4t74ZzgMpTbHOH6mI1zEpMEask/fTZHKOxDLSJ+q16s+1sAvC5yg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/intl-localematcher": "^0.6.2", "@orama/orama": "^3.1.16", @@ -7682,9 +7702,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8202,6 +8222,7 @@ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.548.0.tgz", "integrity": "sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA==", "license": "ISC", + "peer": true, "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -9454,6 +9475,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-16.0.0.tgz", "integrity": "sha512-nYohiNdxGu4OmBzggxy9rczmjIGI+TpR5vbKTsE1HqYwNm1B+YSiugSrFguX6omMOKnDHAmBPY4+8TNJk0Idyg==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.0.0", "@swc/helpers": "0.5.15", @@ -10014,6 +10036,12 @@ ], "license": "MIT" }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -10040,6 +10068,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10049,6 +10078,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10422,6 +10452,32 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resend": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.4.2.tgz", + "integrity": "sha512-YnxmwneltZtjc7Xff+8ZjG1/xPLdstCiqsedgO/JxWTf7vKRAPCx6CkhQ3ZXskG0mrmf8+I5wr/wNRd8PQMUfw==", + "license": "MIT", + "dependencies": { + "svix": "1.76.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -11100,6 +11156,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svix": { + "version": "1.76.1", + "resolved": "https://registry.npmjs.org/svix/-/svix-1.76.1.tgz", + "integrity": "sha512-CRuDWBTgYfDnBLRaZdKp9VuoPcNUq9An14c/k+4YJ15Qc5Grvf66vp0jvTltd4t7OIRj+8lM1DAgvSgvf7hdLw==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "@types/node": "^22.7.5", + "es6-promise": "^4.2.8", + "fast-sha256": "^1.3.0", + "url-parse": "^1.5.10", + "uuid": "^10.0.0" + } + }, + "node_modules/svix/node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/svix/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/svix/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/swr": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", @@ -11128,7 +11226,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -11350,6 +11449,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11604,6 +11704,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -11892,6 +12002,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/docs/package.json b/docs/package.json index f0454dea2..02d7c7b56 100644 --- a/docs/package.json +++ b/docs/package.json @@ -27,6 +27,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-tweet": "^3.2.2", + "resend": "^6.4.2", "tailwind-merge": "^3.3.1", "zod": "^4.1.12" }, diff --git a/docs/public/blog/rybbit_goals_dashboard.png b/docs/public/blog/rybbit_goals_dashboard.png new file mode 100644 index 000000000..df6aa88f4 Binary files /dev/null and b/docs/public/blog/rybbit_goals_dashboard.png differ diff --git a/docs/source.config.ts b/docs/source.config.ts index f11de3762..71a499c34 100644 --- a/docs/source.config.ts +++ b/docs/source.config.ts @@ -28,4 +28,17 @@ export const blog = defineDocs({ }, }); -export default defineConfig(); +// Updates collection - separate from docs +export const updates = defineDocs({ + dir: 'content/updates', + docs: { + schema: frontmatterSchema.extend({ + date: z.string().date().or(z.date()), + author: z.string().optional(), + image: z.string().optional(), + tags: z.array(z.string()).optional(), + }), + }, +}); + +export default defineConfig(); \ No newline at end of file diff --git a/docs/src/app/api/subscribe/route.ts b/docs/src/app/api/subscribe/route.ts new file mode 100644 index 000000000..37ae34d89 --- /dev/null +++ b/docs/src/app/api/subscribe/route.ts @@ -0,0 +1,193 @@ +import { NextRequest, NextResponse } from "next/server"; +import { Resend } from "resend"; +import { z } from "zod"; + +const resend = new Resend(process.env.RESEND_API_KEY); + +// Email validation schema +const subscribeSchema = z.object({ + email: z.string().email("Invalid email address"), +}); + +// Rate limiting map (simple in-memory, for production use Redis) +const rateLimitMap = new Map(); + +// Cleanup expired rate limit entries every 10 minutes +setInterval(() => { + const now = Date.now(); + for (const [email, limit] of rateLimitMap.entries()) { + // Remove entries 10 minutes after their reset time expires + if (now > limit.resetTime + 600000) { + rateLimitMap.delete(email); + } + } +}, 600000); // Run every 10 minutes + +function checkRateLimit(email: string): boolean { + const now = Date.now(); + const limit = rateLimitMap.get(email); + + if (!limit) { + rateLimitMap.set(email, { count: 1, resetTime: now + 60000 }); // 1 minute window + return true; + } + + if (now > limit.resetTime) { + rateLimitMap.set(email, { count: 1, resetTime: now + 60000 }); + return true; + } + + if (limit.count >= 5) { + return false; + } + + limit.count++; + return true; +} + +export async function POST(request: NextRequest) { + // Generate unique request ID for debugging + const requestId = crypto.getRandomValues(new Uint8Array(8)).reduce( + (acc, val) => acc + val.toString(16).padStart(2, "0"), + "" + ); + + try { + // Validate Content-Type + const contentType = request.headers.get("content-type"); + if (!contentType?.includes("application/json")) { + return NextResponse.json( + { error: "Content-Type must be application/json" }, + { status: 400 } + ); + } + + const body = await request.json(); + + // Validate request body with zod + const validatedData = subscribeSchema.parse(body); + const { email } = validatedData; + + // Check rate limiting + if (!checkRateLimit(email)) { + return NextResponse.json( + { error: "Too many requests. Please try again later." }, + { status: 429 } + ); + } + + const segmentId = process.env.RESEND_SEGMENT_ID; + + if (!segmentId) { + console.warn("RESEND_SEGMENT_ID not configured"); + return NextResponse.json( + { error: "Subscription service not configured" }, + { status: 500 } + ); + } + + // First, create the contact + let contactId: string | undefined; + try { + // Extract and sanitize firstName from email + const firstName = email + .split("@")[0] + .replace(/[^a-zA-Z0-9._-]/g, "") // Remove special characters + .substring(0, 50); // Limit length + + const contactResponse = await resend.contacts.create({ + email, + firstName: firstName || "Subscriber", // Fallback if all chars removed + unsubscribed: false, + }); + + console.log(`[${requestId}] Contact creation response:`, { + success: !contactResponse.error, + hasId: !!contactResponse.data?.id, + }); + + if (contactResponse.error) { + console.error(`[${requestId}] Could not create contact`); + return NextResponse.json( + { error: "Failed to create subscriber" }, + { status: 500 } + ); + } + + if (contactResponse.data?.id) { + contactId = contactResponse.data.id; + } + } catch (contactError) { + console.error(`[${requestId}] Error creating contact:`, contactError instanceof Error ? contactError.message : "Unknown error"); + return NextResponse.json( + { error: "Failed to create subscriber" }, + { status: 500 } + ); + } + + // Try to add contact to segment + try { + const segmentResponse = contactId + ? await resend.contacts.segments.add({ + contactId, + segmentId, + }) + : await resend.contacts.segments.add({ + email, + segmentId, + }); + + console.log(`[${requestId}] Segment add response:`, { + success: !segmentResponse.error, + }); + + if (segmentResponse.error) { + console.error(`[${requestId}] Could not add contact to segment`); + return NextResponse.json( + { error: "Failed to add to newsletter" }, + { status: 500 } + ); + } + } catch (segmentError) { + console.error(`[${requestId}] Error adding contact to segment:`, segmentError instanceof Error ? segmentError.message : "Unknown error"); + return NextResponse.json( + { error: "Failed to add to newsletter" }, + { status: 500 } + ); + } + + // Log subscription without exposing full email for privacy + // Mask middle part of email, keeping first char and domain + const [localPart, domain] = email.split("@"); + const maskedLocal = localPart.length <= 2 + ? "***" + : localPart.charAt(0) + "***" + localPart.charAt(localPart.length - 1); + const maskedEmail = `${maskedLocal}@${domain}`; + console.log(`New subscriber added via Resend: ${maskedEmail}`, { + subscribedAt: new Date().toISOString(), + }); + + return NextResponse.json( + { + success: true, + message: "Successfully subscribed to the newsletter", + }, + { status: 200 } + ); + } catch (error) { + // Handle validation errors + if (error instanceof z.ZodError) { + const firstError = error.issues[0]; + return NextResponse.json( + { error: firstError?.message || "Invalid request" }, + { status: 400 } + ); + } + + console.error(`[${requestId}] Error processing subscription:`, error instanceof Error ? error.message : "Unknown error"); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx index 8ea29f0ac..b7ad013d1 100644 --- a/docs/src/app/layout.tsx +++ b/docs/src/app/layout.tsx @@ -1,4 +1,5 @@ import "@/app/global.css"; +import { NewsletterPopup } from "@/components/NewsletterPopup"; import { RootProvider } from "fumadocs-ui/provider/next"; import { Inter } from "next/font/google"; import Script from "next/script"; @@ -113,6 +114,7 @@ export default function Layout({ children }: { children: ReactNode }) { enableSystem: true, }} > + {children} diff --git a/docs/src/app/updates/[...slug]/page.tsx b/docs/src/app/updates/[...slug]/page.tsx new file mode 100644 index 000000000..b1ab8b386 --- /dev/null +++ b/docs/src/app/updates/[...slug]/page.tsx @@ -0,0 +1,188 @@ +import { notFound } from "next/navigation"; +import { updatesSource } from "@/lib/updates-source"; +import Link from "next/link"; +import Image from "next/image"; +import { ChevronLeft } from "lucide-react"; +import type { Metadata } from "next"; +import defaultMdxComponents from "fumadocs-ui/mdx"; +import { Pre } from "fumadocs-ui/components/codeblock"; +import Script from "next/script"; + +export function generateStaticParams() { + return updatesSource.getPages().map(page => ({ + slug: page.slugs, + })); +} + +export async function generateMetadata(props: { params: Promise<{ slug: string[] }> }): Promise { + const params = await props.params; + const page = updatesSource.getPage(params.slug); + if (!page) return {}; + + const url = `https://docs.rybbit.io/updates/${params.slug.join("/")}`; + const publishedTime = page.data.date ? new Date(page.data.date).toISOString() : undefined; + const ogImage = page.data.image || "/opengraph-image.png"; + + return { + title: page.data.title, + description: page.data.description, + alternates: { + canonical: url, + }, + openGraph: { + title: page.data.title, + description: page.data.description, + type: "article", + publishedTime, + authors: page.data.author ? [page.data.author] : undefined, + url, + images: [ + { + url: ogImage, + width: 1200, + height: 630, + alt: page.data.title, + }, + ], + siteName: "Rybbit", + locale: "en_US", + }, + twitter: { + card: "summary_large_image", + title: page.data.title, + description: page.data.description, + images: [ogImage], + creator: "@rybbitio", + }, + keywords: page.data.tags + ? [...page.data.tags, "product update", "Rybbit"] + : ["product update", "Rybbit"], + authors: page.data.author ? [{ name: page.data.author }] : undefined, + }; +} + +export default async function UpdatePostPage(props: { params: Promise<{ slug: string[] }> }) { + const params = await props.params; + const page = updatesSource.getPage(params.slug); + + if (!page) { + notFound(); + } + + const MDXContent = page.data.body; + const date = page.data.date ? new Date(page.data.date) : null; + + // Structured data for SEO + const structuredData = { + "@context": "https://schema.org", + "@type": "BlogPosting", + headline: page.data.title, + description: page.data.description, + datePublished: date?.toISOString(), + dateModified: date?.toISOString(), + author: { + "@type": "Person", + name: page.data.author || "Rybbit Team", + }, + publisher: { + "@type": "Organization", + name: "Rybbit", + logo: { + "@type": "ImageObject", + url: "https://rybbit.com/public/rybbit.svg", + }, + }, + keywords: page.data.tags?.join(", "), + }; + + return ( + <> +