Migrate blog from Gatsby to Astro#3
Conversation
- Replaced Gatsby with Astro 4.x as the static site generator - Migrated all blog posts, guides, pages, and presentations to Astro content collections - Converted React components to Astro components (Nav, Footer, layouts) - Set up Astro integrations: sitemap, RSS feed, MDX, and React support - Updated image paths from relative to absolute for compatibility - Configured TypeScript support with strict mode - Successfully built and generated static site with 30 pages - Maintained all existing content and structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
WalkthroughThis PR migrates the site from Gatsby to Astro: removes Gatsby configs/components, adds Astro config, layouts, pages (including a dynamic [...slug] route with getStaticPaths), new styling (brutalist + dark mode), CI workflows for Firebase previews/production, package/TS updates, and assorted content/path fixes. Changes
sequenceDiagram
participant Browser as Client (Browser)
participant AstroRouter as Astro Routing
participant GSP as getStaticPaths (build)
participant Collections as Content Collections
participant Renderer as entry.render()
participant Layout as BaseLayout
Note over AstroRouter,Collections: Dynamic route at src/pages/[...slug].astro
Browser->>AstroRouter: Request /some-slug
par Build-time path resolution
AstroRouter->>GSP: getStaticPaths()
GSP->>Collections: query posts/pages/guides/presentations
Collections-->>GSP: return entries (slug, type, entry)
end
AstroRouter->>Collections: resolve entry for slug (runtime/static)
Collections-->>Renderer: provide entry
Renderer->>Layout: render content and inject meta (title, description, image)
Layout-->>Browser: Serve composed HTML with Nav and Footer
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Repository UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 20
🧹 Nitpick comments (21)
src/new-moon.css.bak (1)
66-77: Simplify redundant media query.The media query redefines
font-size: 0.9remfor both code and pre elements—the exact same value as the base rule (line 33). You can remove the redundant font-size redeclaration inside the media query, keeping only the layout adjustments (padding, margin, border-radius).@media screen and (min-width: 800px) { - code[class*='language-'], - pre[class*='language-'] { - font-size: 0.9rem; - } pre[class*='language-'] { padding: 1.5rem; margin-left: auto; margin-right: auto; border-radius: 0.35rem; } }src/content/guides/cloud-run-mysql-tracing.md (2)
116-118: Fix code‑block languages for syntax highlighting + copy/paste DXA few blocks are mislabeled (json/docker) which hurts readability and copyability. Let’s switch to the right languages.
-```json +```sh gcloud config set project [PROJECT_ID]-
json +sh
git clone https://github.com/aravindputrevu/elastic-cloud-samples.git-```json +```properties spring.cloud.gcp.sql.database-name= spring.cloud.gcp.sql.instance-connection-name= spring.datasource.password=-
json +properties
service_name=attendance-tracker
application_packages=cloudcode.helloworld
server_url=
secret_token=-```docker +```sh gcloud run services delete [SERVICE_NAME]-
docker +sh
gcloud sql instances delete [INSTANCE_NAME]Also applies to: 126-128, 169-174, 186-191, 271-277
68-68: Quick copy edits for clarity and professionalismTiny polishing pass to reduce reader friction.
-... MySQL, PostgresSQL, and SQL Server. +... MySQL, PostgreSQL, and SQL Server.-Elastic launched an [Open source Application Performance Monitoring (APM)] +Elastic launched an [open‑source application performance monitoring (APM)]-... have DB in mutliple zones. I went ahead with ... +... have the DB in multiple zones. I proceeded with ...-[Youtube video of on how to use Cloud shell] +[YouTube video on how to use Cloud Shell]Also applies to: 76-76, 98-98, 291-291
src/style.css.bak (1)
634-636: Gatsby leftovers and fixed TOC positioning — lightweight cleanup
- Selectors like a.gatsby-resp-image-link, .gatsby-image-wrapper, and .anchor.before are Gatsby-specific. If unused in Astro, consider pruning to reduce CSS bloat.
- The .toc/.toc-heading use fixed positioning with calc(50% + 400px) which will overlap on narrow screens. Prefer position: sticky with a right offset, and hide on small viewports.
-.toc{ - position: fixed; - left: calc(50% + 400px); - top: 420px; - max-height: 40vh; - width: 340px; - display: flex; - overflow: hidden; - overflow-y: scroll; -} +@media (min-width: 1100px) { + .toc { + position: sticky; + top: 6rem; + max-height: 40vh; + width: 320px; + margin-left: 2rem; + overflow: auto; + } +} +@media (max-width: 1099px) { + .toc { display: none; } +}Also applies to: 1091-1106, 1361-1373, 1430-1449
package.json (1)
10-16: Tighten Node/CI ergonomics: deps split, engines, scriptsA couple tweaks make this repo friendlier for contributors and CI.
- Move @types/*, typescript, and @astrojs/check to devDependencies.
- Add engines to avoid surprise Node versions with Astro 4.
- Prefer start = astro preview for production.
- Generate content types on install.
"scripts": { - "dev": "astro dev", - "start": "astro dev", + "dev": "astro dev", + "start": "astro preview", "build": "astro check && astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "postinstall": "astro sync", + "format": "prettier -w ." }, + "engines": { + "node": ">=18.14.1" + }, "dependencies": { - "@astrojs/check": "^0.9.4", - "@astrojs/mdx": "^3.1.8", + "@astrojs/mdx": "^3.1.8", "@astrojs/react": "^3.6.2", "@astrojs/rss": "^4.0.9", "@astrojs/sitemap": "^3.2.1", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", "astro": "^4.16.14", "react": "^18.3.1", "react-dom": "^18.3.1", - "typescript": "^5.6.3" + "typescript": "^5.6.3" }, "devDependencies": { - "prettier": "^3.3.3", - "prettier-plugin-astro": "^0.14.1" + "prettier": "^3.3.3", + "prettier-plugin-astro": "^0.14.1", + "@astrojs/check": "^0.9.4", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "typescript": "^5.6.3" },If you want, I can generate a quick script to validate that no tooling relies on start=dev in your deployment target.
Also applies to: 18-29, 31-33
src/content/presentations/getting-started-azure-elasticsearch.md (1)
16-16: Make the embedded Slides responsive and accessibleTweak the iframe to be semantic and mobile-friendly.
-<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vS8tUGJQGhBk2iBDzptuiHGh77BgZFF50Jlc3eGB6-D7E4L0eJOSJb9xfoxdIv3LCEYgeztNgyvjtmn/embed?start=false&loop=false&delayms=3000" frameBorder="0" width="748" height="450" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> +<iframe + src="https://docs.google.com/presentation/d/e/2PACX-1vS8tUGJQGhBk2iBDzptuiHGh77BgZFF50Jlc3eGB6-D7E4L0eJOSJb9xfoxdIv3LCEYgeztNgyvjtmn/embed?start=false&loop=false&delayms=3000" + title="Getting Started with Elasticsearch on Azure — slides" + frameborder="0" + loading="lazy" + allowfullscreen + style="width:100%; aspect-ratio: 16/9; height: auto;" +></iframe>src/content/presentations/building-search-exp-python.md (1)
16-16: Same here: responsive, accessible iframeLet’s apply the same improvements to this embed.
-<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vRgOd0Ac79DwdCwg48Eyi36HIJ9ziLLb8Um_lSE-b7gaDgr0Kd099G8aRxM7hHH4ilMy5wom_K7r0tK/embed?start=false&loop=false&delayms=3000" frameBorder="0" width="765" height="450" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> +<iframe + src="https://docs.google.com/presentation/d/e/2PACX-1vRgOd0Ac79DwdCwg48Eyi36HIJ9ziLLb8Um_lSE-b7gaDgr0Kd099G8aRxM7hHH4ilMy5wom_K7r0tK/embed?start=false&loop=false&delayms=3000" + title="Building a Search Experience for a Python Web Application — slides" + frameborder="0" + loading="lazy" + allowfullscreen + style="width:100%; aspect-ratio: 16/9; height: auto;" +></iframe>src/pages/404.astro (1)
5-15: Clean 404 — nice!Looks great. Optional: add aria-label to the “Go back home” link for screen readers, but it’s already clear.
- <p><a href="/">Go back home</a></p> + <p><a href="/" aria-label="Go back to the homepage">Go back home</a></p>src/content/posts/kid-coding-mandatory.md (1)
15-19: Level up these bare URLs with proper Markdown links! 🔗You've got some bare URLs wrapped in HTML divs here. For better accessibility and cleaner rendering, I'd recommend converting these to proper Markdown links or embed syntax. Your screen reader users (and your Markdown linter) will thank you!
For example, line 17 could become:
[View Twitter Poll](https://twitter.com/aravindputrevu/status/1308747154932948998)Same goes for the YouTube link on line 46 and the Twitter link on line 59. This improves the semantic structure and makes the content more accessible!
Based on static analysis hints.
Also applies to: 57-61
src/components/astro/Nav.astro (2)
46-54: Potential FOUC (Flash of Unstyled Content) with dynamic stylesheet injection! 💡Hey! I notice you're dynamically injecting the
dark.cssstylesheet when toggling to dark mode. This approach might cause a brief flash of unstyled content while the CSS loads, which isn't the best user experience.A more robust pattern would be to:
- Always include the dark mode CSS in your main stylesheet with scoped selectors (like
.dark .your-element)- Just toggle the
darkclass (which you're already doing!)- Let CSS handle the theme switching instantly
This eliminates the network request and potential rendering delay. Plus, it's consistent with how modern dark mode implementations work (like in Tailwind CSS).
Check out how BaseLayout might already be handling this - the AI summary mentions it includes a dark-theme script on load. We might be able to consolidate this logic!
26-57: Consolidate theme initialization to a single locationThe duplicate initialization is confirmed! Both
BaseLayout.astro(lines 49-52) andNav.astro(lines 29-34) are reading from localStorage and applying the dark class todocumentElement. SinceNav.astroincludes the more complete initialization (including button text), theBaseLayout.astroinitialization is strictly redundant.Remove the theme initialization from
BaseLayout.astroand keep only the one inNav.astroto eliminate the unnecessary localStorage read and DOM manipulation.src/content/posts/open-source-sustainability.md (1)
48-48: Consider: You're loading the Twitter widgets script twice! 📱I see you've got the Twitter widgets script (
platform.twitter.com/widgets.js) embedded twice in your post - once on line 48 and again on line 61. The browser should handle this gracefully, but it's a bit redundant.Most markdown renderers will load the Twitter widget script automatically for all tweets on the page, so you might be able to remove these inline script tags entirely and let Astro or a plugin handle it globally. Just a thought for cleaner content!
Also applies to: 61-61
src/content/posts/everything-index-elastic.md (1)
85-85: Consider fixing the heading hierarchy for better accessibility! 📚Hey! I noticed you're jumping from
h2(##) directly toh4(####) on lines 85 and 121. This violates the heading hierarchy and can confuse screen readers and hurt SEO.For better document structure and accessibility, headings should increment by one level at a time. Consider changing these
####headings to###unless they're truly nested under a parenth3.Based on static analysis hints.
Also applies to: 121-121
src/pages/[...slug].astro (2)
30-31: Smart title construction! But let's improve the description! 📝I love how you're building a descriptive page title here! However, for the
description(which typically goes into meta tags for SEO), you're just using thetitleagain.If your content collections have a
descriptionfield in the front matter (like I see in some of the post examples), you should use that instead:const title = `${entry.data.title} - Aravind Putrevu`; -const description = entry.data.title; +const description = entry.data.description || entry.data.title;This gives you better, unique meta descriptions for each page, which is great for SEO and social media sharing! 🚀
43-43: TypeScript note: That tag type assertion might be unnecessary! 🔷You've got
(tag: string)in the map function, but if your content schema properly definestagsasstring[]insrc/content/config.ts, TypeScript should already infer this. The type assertion is harmless but might be redundant.Just a small note for cleaner code - you could probably remove it! But if it makes the code clearer to you, keeping it is totally fine. ✨
src/pages/index.astro (4)
15-16: Rename the section or broaden the filter so the UI matches the data
- Right now you filter by tag 'tech' but label it "Latest Blog Posts". Either remove the tag filter or rename the heading. Your call—both are fine.
Option A (show true latest, no tag filter):
-const latestPosts = sortedPosts.filter(post => post.data.tags?.includes('tech')).slice(0, 5); +const latestPosts = sortedPosts.slice(0, 5);Option B (keep the filter, rename the heading):
- <h2> - Latest Blog Posts + <h2> + Latest Tech PostsLike it. Ship it. But make it consistent.
Also applies to: 74-76
11-13: Avoid mutating arrays with sort to prevent sneaky future bugs
Array.prototype.sort mutates. Copy before sorting to keep things predictable.-const sortedPosts = allPosts.sort((a, b) => { +const sortedPosts = [...allPosts].sort((a, b) => { return b.data.date.getTime() - a.data.date.getTime(); }); - -const latestPresentations = allPresentations.sort((a, b) => { +const latestPresentations = [...allPresentations].sort((a, b) => { return (b.data.date?.getTime() || 0) - (a.data.date?.getTime() || 0); }).slice(0, 5);Small change, nicer invariants.
Also applies to: 19-21
66-67: Add image dimensions/priority to improve CLS and LCP
Hero image should declare width/height and fetch priority.- <img src="/images/aravind-2020.jpg" alt="Aravind" /> + <img + src="/images/aravind-2020.jpg" + alt="Aravind" + width="480" + height="480" + loading="eager" + fetchpriority="high" + decoding="async" + />If you can move this image under src/assets, consider Astro’s Image for optimization later.
116-119: Harden the newsletter iframe for a11y and performance
Add a title and lazy‑load the embed.- <iframe class="newsletter-container-size" src="https://devshorts.substack.com/embed" frameborder="0"></iframe> + <iframe + class="newsletter-container-size" + src="https://devshorts.substack.com/embed" + title="Subscribe to DevShorts on Substack" + loading="lazy" + frameBorder="0" + ></iframe>Bonus: consider sandbox attrs if Substack allows.
src/content/config.ts (2)
3-15: Use image() for typed images (and require alt when image present)
Typed images unlock transforms and safer content. Also nudge authors to include alt text.-import { defineCollection, z } from 'astro:content'; +import { defineCollection, z, image } from 'astro:content'; @@ schema: z.object({ title: z.string(), date: z.date(), template: z.string().optional(), hidden: z.boolean().default(false), - thumbnail: z.string().optional(), - featuredImage: z.string().optional(), + thumbnail: image().optional(), + featuredImage: image().optional(), + featuredImageAlt: z.string().optional(), tags: z.array(z.string()).optional(), categories: z.array(z.string()).optional(), }),Optional follow‑up: refine so if featuredImage is set, featuredImageAlt is required.
25-33: Add hidden/draft across collections for consistent filtering
You already support hidden on posts; mirror it for guides and presentations to keep previews under control.const guidesCollection = defineCollection({ type: 'content', schema: z.object({ title: z.string(), date: z.date().optional(), + hidden: z.boolean().default(false), tags: z.array(z.string()).optional(), }), }); @@ const presentationsCollection = defineCollection({ type: 'content', schema: z.object({ title: z.string(), date: z.date().optional(), + hidden: z.boolean().default(false), tags: z.array(z.string()).optional(), }), });Then you can reuse the same
({ data }) => !data.hiddenfilter everywhere.Also applies to: 34-41
| .newletter-container-size { | ||
| width: 760px; | ||
| height: 150px; | ||
| border: "1px solid #EEE"; | ||
| background: "white" | ||
| } | ||
|
|
||
| .newsleter-form-field { | ||
| font-family: Roboto; | ||
| font-size: 18px; |
There was a problem hiding this comment.
Invalid CSS (quoted values) and class name typos break styles
Borders/backgrounds shouldn’t be quoted, and the class names are misspelled (newsletter).
-.newletter-container-size {
+.newsletter-container-size {
width: 760px;
height: 150px;
- border: "1px solid #EEE";
- background: "white"
+ border: 1px solid #EEE;
+ background: white;
}
-.newsleter-form-field {
+.newsletter-form-field {
font-family: Roboto;
font-size: 18px;
line-height: 14px;
font-weight: 400;
font-style: normal;
text-align: left;
padding-right: 4px;
padding-left: 4px;
padding-bottom: 4px;
padding-top: 4px;
border-color: #5D7CFA;
border-top-style: none;
border-right-style: none;
border-bottom-style: solid;
border-left-style: none;
- background-color: #ffffff
+ background-color: #ffffff;
}Also applies to: 167-184
🤖 Prompt for AI Agents
In src/style.css.bak around lines 160-169 (and similarly 167-184), the CSS uses
quoted property values and misspelled class names which break styles; rename
classes to .newsletter-container-size and .newsletter-form-field, remove the
quotes from border and background values (use border: 1px solid #EEE;
background: white;), ensure proper semicolons and closing braces, and provide a
valid font-family declaration (e.g., font-family: "Roboto", sans-serif;) for the
form field.
- Updated Firebase config to use Astro's dist/ output directory - Configured clean URLs and removed unnecessary rewrites - Updated production deployment workflow for main branch - Uses Node.js 20 with dependency caching - Runs Astro build instead of Gatsby - Added PR preview workflow - Automatically deploys preview for pull requests - Preview expires after 7 days - Added branch preview workflow - Deploys preview for all feature branches - Provides unique preview URL per branch - Updated .gitignore for Astro (dist/ and .astro/) - Added public static assets Branch previews will be available at unique URLs once merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
public/logo.pngis excluded by!**/*.png
📒 Files selected for processing (7)
.github/workflows/firebase-hosting-branch.yml(1 hunks).github/workflows/firebase-hosting-merge.yml(1 hunks).github/workflows/firebase-hosting-pull-request.yml(1 hunks).gitignore(1 hunks)firebase.json(1 hunks)public/dark.css(1 hunks)public/robots.txt(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- public/robots.txt
🧰 Additional context used
🪛 Biome (2.1.2)
public/dark.css
[error] 118-118: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
border is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
🔇 Additional comments (5)
.gitignore (1)
57-64: Astro migrations are pretty great—clean structure here!The approach to mark Gatsby as "legacy" while adding Astro-specific entries is thoughtful. A couple of things to verify:
Verify the Astro output directories: In standard Astro setups,
dist/is the built output and.astro/contains metadata. Ispublic/images/andpublic/thumbnails/part of your Astro config, or are these processed assets? (Just want to make sure you're ignoring the right things.)Public directory handling: Astro treats
public/as a source directory that gets copied as-is to the root ofdist/. If you're not explicitly ignoringpublic/itself, that's actually correct—but just double-check that you want subdirectories likepublic/images/andpublic/thumbnails/to be ignored rather than included in version control.If these are generated/cached directories (sounds like they are!), you're solid. If they're part of your source content, you might want to include them in the repo instead.
firebase.json (1)
3-3: Great alignment with Astro's static output.The shift from
publictodistis exactly what you want—that's where Astro puts the goods. ThecleanUrlssetting automatically drops the .html extension from uploaded file URLs, andtrailingSlash: falsekeeps URLs tidy without trailing slashes. Since you're building a static site (not an SPA), ditching the rewrites to/index.htmlis the right call.Also applies to: 10-11
.github/workflows/firebase-hosting-merge.yml (1)
13-27: Solid modernization of the production workflow.You've bumped the actions to v4, locked in Node 20, and switched to
npm cifor a cleaner, more deterministic build pipeline. The explicit "Build Astro site" step is a nice touch—makes it crystal clear what's going down. The deploy config withtarget,projectId, andchannelIdis all you need for static Firebase hosting..github/workflows/firebase-hosting-branch.yml (1)
1-48: Clean branch preview deployment setup.This workflow mirrors the merge flow nicely—modern actions, Node 20, npm ci, and the Astro build step all locked in. The
channelId: ${{ github.ref_name }}is clever for organizing previews by branch. One heads-up: the preview URL script at the end (lines 44-48) constructs the URL based on branch name and commit SHA. Verify this aligns with Firebase's actual preview URL scheme—if Firebase ever changes its URL pattern, you'll want to keep that script in sync or pull the actual URL from the deploy output..github/workflows/firebase-hosting-pull-request.yml (1)
1-37: Solid PR preview flow with a nice security guardrail.The guard check on line 10 (
github.event.pull_request.head.repo.full_name == github.repository) is exactly what you want—prevents deploying PRs from forks without auth. The rest follows your merge/branch pattern: Node 20, npm ci, Astro build, and Firebase deploy. The 7-day expiration for PR previews is a sensible default.
| [type='search'] { | ||
| border: 1px solid var(--light-background); | ||
| background: var(--light-background); | ||
| color: var(--dark-font-color); | ||
| border: 2px solid transparent; | ||
| } | ||
|
|
||
| [type='search']:focus { | ||
| outline: none; | ||
| border: 2px solid var(--link-color); | ||
| } |
There was a problem hiding this comment.
Fix the duplicate border property and add missing semicolon.
You've got two border properties in the [type='search'] rule—that second one on line 118 will override the first, which is probably not what you meant. Looks like you intended line 118 to set border: 2px solid transparent for the focus state, so it should live inside the :focus rule below. Also, line 150's missing semicolon should be added.
Apply this diff to clean it up:
[type='search'] {
border: 1px solid var(--light-background);
background: var(--light-background);
color: var(--dark-font-color);
- border: 2px solid transparent;
}
[type='search']:focus {
outline: none;
border: 2px solid var(--link-color);
}
header u {
background: linear-gradient(transparent 85%, #111 0);
}
table,
td,
th {
border-color: #111;
}
tbody tr:nth-child(odd) {
background-color: #222;
}
blockquote {
color: rgba(255, 255, 255, 0.9);
}
.new-post {
background: #37b24d;
color: white;
}
.newsleter-form-field {
- background-color: #2B2B2B
+ background-color: #2B2B2B;
}Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Biome (2.1.2)
[error] 118-118: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
border is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
🤖 Prompt for AI Agents
In public/dark.css around lines 114–124, the [type='search'] rule contains a
duplicate border declaration (the 2px transparent one overriding the intended
1px solid) and the 2px transparent border belongs in the :focus rule; remove the
second border from the base selector so it only has border: 1px solid
var(--light-background); and add border: 2px solid var(--link-color); to the
[type='search']:focus rule (keeping outline: none). Also add the missing
semicolon at line 150 as noted.
|
@coderabbitai how to test this PR? |
|
Tip For best results, initiate chat on the files or code changes. @aravindputrevu Hey there! Great question! Let me walk you through testing this Astro migration. Here's what I'd recommend: 🧪 Testing Steps1. Clone and Setupgit checkout claude/migrate-blog-to-astro-011CUbbn2h14AZoCwmyvTUTf
npm install2. Start Development Servernpm run devThis will start the Astro dev server (usually on 3. Manual Testing ChecklistNavigate through the site and verify:
4. Build and Previewnpm run build
npm run previewThe build command runs What to check:
5. Check Outputls -la dist/Verify the
6. Validate Sitemap & RSS
7. Compare with ProductionIf the current site is live, compare a few pages side-by-side to ensure:
🚨 Common Issues to Watch For
That's the testing approach I'd take! Let me know if you run into any issues or if you'd like me to help debug something specific! 🚀 |
- Remove content/ directory (markdown already in src/content/, images moved to public/) - Delete Gatsby config files (gatsby-browser.js, gatsby-node.js, gatsby-config.js) - Delete old React components (src/components/*.js) replaced by Astro components - Remove ESLint configuration (.eslintrc.js, .eslintignore) - Delete static/ directory (duplicated in public/) - Update .gitignore to track public/images/ and public/thumbnails/ (Astro pattern) - Update Footer link from /guides to /archive Images are now served from public/ (Astro's static asset directory) instead of content/ (Gatsby's pattern where public/ was build output). code written with the help of Claude code
Update site navigation to use "Writing" instead of "Blogs" and replace emoji dark mode toggle with minimal symbols. Redesign footer with simplified layout and updated social links. Create new about page and remove unused guides and presentations pages to streamline site structure. code written with the help of Claude code
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (145)
dist/404.htmlis excluded by!**/dist/**dist/about/index.htmlis excluded by!**/dist/**dist/blog/index.htmlis excluded by!**/dist/**dist/building-o11y-infra-code/index.htmlis excluded by!**/dist/**dist/building-search-exp-python/index.htmlis excluded by!**/dist/**dist/cloud-run-mysql-tracing/index.htmlis excluded by!**/dist/**dist/dark.cssis excluded by!**/dist/**dist/data-leaks-tips/index.htmlis excluded by!**/dist/**dist/elastic-app-search-python/index.htmlis excluded by!**/dist/**dist/elastic-cloud-apm/index.htmlis excluded by!**/dist/**dist/elastic-data-ingest/index.htmlis excluded by!**/dist/**dist/elastic-data-stream/index.htmlis excluded by!**/dist/**dist/elastic-ilm-test/index.htmlis excluded by!**/dist/**dist/everything-index-elastic/index.htmlis excluded by!**/dist/**dist/four-years-elastic/index.htmlis excluded by!**/dist/**dist/getting-started-azure-es/index.htmlis excluded by!**/dist/**dist/index.htmlis excluded by!**/dist/**dist/kid-coding-mandatory/index.htmlis excluded by!**/dist/**dist/layoff/index.htmlis excluded by!**/dist/**dist/nextjs-tailwind-elastic/index.htmlis excluded by!**/dist/**dist/open-source-alternative/index.htmlis excluded by!**/dist/**dist/open-source-sustainability/index.htmlis excluded by!**/dist/**dist/running-elasticsearch-k8s/index.htmlis excluded by!**/dist/**dist/serverless-everywhere/index.htmlis excluded by!**/dist/**dist/sitemap-0.xmlis excluded by!**/dist/**dist/three-years-elastic/index.htmlis excluded by!**/dist/**dist/tracing-java-apps-elastic-apm/index.htmlis excluded by!**/dist/**dist/vaccine-for-all/index.htmlis excluded by!**/dist/**dist/weaponise-everything/index.htmlis excluded by!**/dist/**dist/working-from-home/index.htmlis excluded by!**/dist/**public/images/aravind-2020.jpgis excluded by!**/*.jpgpublic/images/code-flat.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-act-shell.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-cloud-sql-db.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-cloud-vs-code.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-create-instances.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-deploy-page.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-deploy-run.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-ess-apm-page.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-ess-latency.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-high-arch.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-jvm-stats.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-open-editor.pngis excluded by!**/*.pngpublic/images/post-images/cloud-run-mysql-tracing/cloud-run-spans.pngis excluded by!**/*.pngpublic/images/post-images/covid-vaccine-blog-image.jpegis excluded by!**/*.jpegpublic/images/post-images/covid-vaccine.jpegis excluded by!**/*.jpegpublic/images/post-images/data-leak.jpgis excluded by!**/*.jpgpublic/images/post-images/diplomacy.jpegis excluded by!**/*.jpegpublic/images/post-images/elastic-3-years.jpgis excluded by!**/*.jpgpublic/images/post-images/elastic-apm-gs/ess-apm-launch.pngis excluded by!**/*.pngpublic/images/post-images/elastic-apm-gs/ess-apm-page.pngis excluded by!**/*.pngpublic/images/post-images/elastic-apm-gs/ess-cloud-console.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image1.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image10.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image11.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image12.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image13.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image14.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image15.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image16.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image17.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image18.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image19.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image2.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image20.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image21.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image22.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image23.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image24.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image25.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image26.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image27.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image28.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image29.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image3.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image30.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image31.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image32.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image33.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image34.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image35.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image36.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image4.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image5.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image6.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image7.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image8.pngis excluded by!**/*.pngpublic/images/post-images/elastic-app-search-python/image9.pngis excluded by!**/*.pngpublic/images/post-images/elastic-data-ingest/1-seven-beats.pngis excluded by!**/*.pngpublic/images/post-images/elastic-data-ingest/2-modules.pngis excluded by!**/*.pngpublic/images/post-images/elastic-data-ingest/3-oss-connectors.pngis excluded by!**/*.pngpublic/images/post-images/elastic-data-ingest/4-integrations.pngis excluded by!**/*.pngpublic/images/post-images/elastic-data-ingest/elastic-data-ingest.jpgis excluded by!**/*.jpgpublic/images/post-images/elastic-ilm-test/es-customize.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/ess-capacity.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/ess-create-deployment.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/ess-login-page.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/ess-observability.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/ess-warm-tier.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/hot-warm-ilm.jpgis excluded by!**/*.jpgpublic/images/post-images/elastic-ilm-test/ilm-indices-nodes.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/kibana-launch.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/kibana-left-menu.pngis excluded by!**/*.pngpublic/images/post-images/elastic-ilm-test/stack-monitoring.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-after-runtime.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-before-runtime.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-data-mapping.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-data-stream.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-ilm.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-index-cluster.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-index.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-indx-shard.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-sql-dsl.pngis excluded by!**/*.pngpublic/images/post-images/everything-index-elastic/es-templates.pngis excluded by!**/*.pngpublic/images/post-images/layoff.pngis excluded by!**/*.pngpublic/images/post-images/nextjs-tailwind-elastic/data-ingested-es.pngis excluded by!**/*.pngpublic/images/post-images/nextjs-tailwind-elastic/hello-api.pngis excluded by!**/*.pngpublic/images/post-images/nextjs-tailwind-elastic/hello-world.pngis excluded by!**/*.pngpublic/images/post-images/nextjs-tailwind-elastic/ingest-data-es.pngis excluded by!**/*.pngpublic/images/post-images/nextjs-tailwind-elastic/nextjs-tailwindcss-es-arch.pngis excluded by!**/*.pngpublic/images/post-images/nextjs-tailwind-elastic/search-ui.pngis excluded by!**/*.pngpublic/images/post-images/nextjs-tailwind-elastic/tailwindcss-intro.pngis excluded by!**/*.pngpublic/images/post-images/open-source-alternative.jpgis excluded by!**/*.jpgpublic/images/post-images/open-source-sustainability.jpgis excluded by!**/*.jpgpublic/images/post-images/serverless-transition.jpgis excluded by!**/*.jpgpublic/images/post-images/work-from-home.jpgis excluded by!**/*.jpgpublic/thumbnails/app-search.pngis excluded by!**/*.pngpublic/thumbnails/astro.pngis excluded by!**/*.pngpublic/thumbnails/binary.pngis excluded by!**/*.pngpublic/thumbnails/community.pngis excluded by!**/*.pngpublic/thumbnails/elastic.pngis excluded by!**/*.pngpublic/thumbnails/favicon.pngis excluded by!**/*.pngpublic/thumbnails/gatsby.pngis excluded by!**/*.pngpublic/thumbnails/geopolitics.pngis excluded by!**/*.pngpublic/thumbnails/github.pngis excluded by!**/*.pngpublic/thumbnails/google-cloud-run.pngis excluded by!**/*.pngpublic/thumbnails/google-cloud.pngis excluded by!**/*.pngpublic/thumbnails/heartbeat.pngis excluded by!**/*.pngpublic/thumbnails/nextjs.pngis excluded by!**/*.pngpublic/thumbnails/open-source.pngis excluded by!**/*.pngpublic/thumbnails/original-tag.pngis excluded by!**/*.pngpublic/thumbnails/python.pngis excluded by!**/*.pngpublic/thumbnails/serverless.pngis excluded by!**/*.pngpublic/thumbnails/shield.pngis excluded by!**/*.pngpublic/thumbnails/wfh.pngis excluded by!**/*.png
📒 Files selected for processing (26)
.eslintignore.eslintrc.js.gitignoregatsby-browser.jsgatsby-config.jsgatsby-node.jspublic/dark.csspublic/~partytown/debug/partytown-atomics.jspublic/~partytown/debug/partytown-media.jspublic/~partytown/debug/partytown-sandbox-sw.jspublic/~partytown/debug/partytown-sw.jspublic/~partytown/debug/partytown-ww-atomics.jspublic/~partytown/debug/partytown-ww-sw.jspublic/~partytown/debug/partytown.jspublic/~partytown/partytown-atomics.jspublic/~partytown/partytown-media.jspublic/~partytown/partytown-sw.jspublic/~partytown/partytown.jssrc/components/Comment.jssrc/components/Footer.jssrc/components/Guides.jssrc/components/Layout.jssrc/components/Nav.jssrc/components/Posts.jssrc/components/Projects.jssrc/components/SEO.js
💤 Files with no reviewable changes (13)
- src/components/Nav.js
- src/components/Posts.js
- gatsby-node.js
- gatsby-browser.js
- src/components/Layout.js
- src/components/Guides.js
- .eslintrc.js
- src/components/Footer.js
- src/components/SEO.js
- gatsby-config.js
- .eslintignore
- src/components/Comment.js
- src/components/Projects.js
🚧 Files skipped from review as they are similar to previous changes (2)
- public/dark.css
- .gitignore
🧰 Additional context used
🧬 Code graph analysis (3)
public/~partytown/debug/partytown-sw.js (1)
public/~partytown/partytown-sw.js (1)
r(2-2)
public/~partytown/debug/partytown.js (2)
public/~partytown/debug/partytown-atomics.js (2)
config(103-103)libPath(104-104)public/~partytown/debug/partytown-sandbox-sw.js (2)
config(103-103)libPath(104-104)
public/~partytown/partytown.js (4)
public/~partytown/partytown-atomics.js (2)
t(2-2)w(2-2)public/~partytown/partytown-media.js (2)
t(2-2)c(2-2)public/~partytown/partytown-sw.js (3)
e(2-2)r(2-2)s(2-2)public/~partytown/debug/partytown-sw.js (1)
r(42-42)
🪛 Biome (2.1.2)
public/~partytown/partytown-sw.js
[error] 2-2: Promise executor functions should not be async.
(lint/suspicious/noAsyncPromiseExecutor)
[error] 2-2: Promise executor functions should not be async.
(lint/suspicious/noAsyncPromiseExecutor)
public/~partytown/debug/partytown-ww-atomics.js
[error] 601-617: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
[error] 883-884: This getter should return a value.
(lint/suspicious/useGetterReturn)
[error] 1285-1299: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
[error] 1697-1704: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
public/~partytown/partytown-media.js
[error] 2-2: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
[error] 2-2: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
[error] 2-2: Can't assign s because it's a constant.
This is where the variable is defined as constant.
Unsafe fix: Replace const with let if you assign it to a new value.
(lint/correctness/noConstAssign)
public/~partytown/debug/partytown-media.js
[error] 110-110: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
[error] 152-154: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
public/~partytown/debug/partytown-ww-sw.js
[error] 593-609: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
[error] 875-876: This getter should return a value.
(lint/suspicious/useGetterReturn)
[error] 1277-1291: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
[error] 1689-1696: The constructor should not return a value.
The constructor is here:
Returning a value from a constructor may confuse users of the class.
(lint/correctness/noConstructorReturn)
public/~partytown/debug/partytown-sw.js
[error] 9-27: Promise executor functions should not be async.
(lint/suspicious/noAsyncPromiseExecutor)
[error] 11-25: Promise executor functions should not be async.
(lint/suspicious/noAsyncPromiseExecutor)
public/~partytown/partytown-atomics.js
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: Can't assign d because it's a constant.
This is where the variable is defined as constant.
Unsafe fix: Replace const with let if you assign it to a new value.
(lint/correctness/noConstAssign)
🔇 Additional comments (10)
public/~partytown/partytown-media.js (1)
1-2: Hey friend! This is vendored Partytown code - looking good! 🎉This is the minified Partytown media runtime that bridges Canvas, Audio, MediaSource, and other media APIs between the worker and main thread. The static analysis warnings about constructor returns and const assignments? Those are totally expected in minified third-party code - Partytown intentionally uses these patterns for its proxy magic.
No changes needed here - just wanted to give you a heads up that these warnings are safe to ignore for vendored files!
public/~partytown/partytown-sw.js (1)
1-2: Vendored Partytown service worker - all good here! ✨This minified service worker handles the
sw.htmlandproxytownfetch interception that makes Partytown's cross-context magic work. The async promise executor pattern flagged by static analysis is intentional - Partytown uses this for its proxy request/response flow. Totally expected in this vendored code!public/~partytown/debug/partytown-sw.js (1)
1-59: Debug version of the Partytown SW - nice to have for troubleshooting! 🔍This is the human-readable debug version of the service worker. Super helpful when you need to trace through what's happening with Partytown! The async promise executor pattern (lines 9-27) is how Partytown coordinates the request/response messaging between contexts - it's intentional and works correctly.
Having both debug and minified versions available is a great developer experience win. When something goes sideways, you can swap in the debug version to get meaningful stack traces!
public/~partytown/debug/partytown-atomics.js (2)
1-10: Partytown atomics runtime - the SharedArrayBuffer magic! 🪄This is the main-thread coordinator for Partytown's atomics mode. It uses
SharedArrayBuffer(check out line 505!) for synchronous cross-context communication - pretty cool stuff for making blocking DOM APIs work from a web worker!
504-506: Cross-origin isolation: the gateway to Atomics mode! 🚀Here's the thing:
SharedArrayBufferis pretty powerful, but it requires your site to be cross-origin isolated. To enable that, your server needs to send the right headers. The classic combo is:Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp(Pro tip:
COEP: credentiallessis also awesome and widely supported if you need to load cross-origin resources without CORP headers.)The cool part? You can verify everything is working by checking
window.crossOriginIsolatedin your browser console. If it'strue, you're in business with Atomics mode!If you can't set up cross-origin isolation, no sweat—Partytown's got your back with an automatic fallback chain: it'll try Service Workers next, and if that's not available, it'll run scripts on the main thread. So you'll still get value from Partytown, just in a different flavor.
public/~partytown/debug/partytown-ww-sw.js (1)
1-57: The worker-side DOM emulation - this is where the magic happens! 🎩This is Partytown's complete worker-side environment that emulates Window, Document, and all those DOM APIs inside a web worker. It's the heart of how Partytown lets you run third-party scripts off the main thread!
The static analysis warnings about constructor returns (like the
CSSStyleDeclarationandNamedNodeMapconstructors) and that emptyhrefgetter? Those are intentional patterns - Partytown returns Proxy objects from constructors to intercept property access and method calls. Pretty clever metaprogramming!public/~partytown/debug/partytown.js (1)
1-75: The Partytown loader - nice and readable debug version! 📖This is the bootstrap loader that gets Partytown up and running. Love how clear the debug version makes the flow:
- Check if we're in an iframe or top window
- Either register the service worker or use atomics mode
- Load the sandbox (iframe or script depending on mode)
- If all else fails, fallback after 10 seconds (line 14)
The forward mechanism (lines 61-68) is clever - it queues up any calls to forwarded APIs before Partytown is ready, then replays them. Great for analytics scripts that might fire immediately!
public/~partytown/debug/partytown-media.js (1)
1-373: This is Partytown's media bridge - looking good! 🎬The static analysis is flagging constructor returns on lines 110 and 152-154, but here's the thing - this is intentional Partytown architecture! The library uses constructor returns to create Proxy wrappers that bridge the Canvas and Media APIs between the web worker and main thread. It's a clever pattern that lets
new Audio(src)actually return a proxied element that works across contexts.This is vendored library code from builder.io, so we shouldn't modify it. The patterns here are battle-tested and work exactly as designed.
public/~partytown/debug/partytown-ww-atomics.js (1)
1-1881: Partytown's web worker runtime - the magic happens here! ✨This file is the heart of Partytown's worker-side DOM virtualization. The static analysis warnings are all false positives for this library code:
Constructor returns (lines 601-617, 1285-1299, 1697-1704): Partytown intentionally returns Proxy objects from constructors to intercept property access and route it through the messaging bridge. This is how
CSSStyleDeclaration,NamedNodeMap, andWorkerTrapProxywork their magic!Empty getter (lines 883-884): The
get href()on the Node class is an intentional no-op. It's part of the base class that gets overridden in specific implementations.This is vendored builder.io code implementing a really sophisticated cross-context DOM proxy layer. Don't modify it - let Partytown do its thing! 🎉
public/~partytown/debug/partytown-sandbox-sw.js (1)
1-559: The main-thread side of Partytown's bridge - nice! 🌉This is the sandbox shim that runs on the main thread and handles:
- Serializing/deserializing data between main thread and worker
- Instance tracking with cleanup for disconnected nodes (line 92-97 - clever!)
- Window registration and history/navigation event forwarding
- The message routing protocol that makes everything work together
Clean vendored code from builder.io. No modifications needed - it's doing exactly what it should!
One small note for the team: these debug versions are great for development, but for production you'll want to use the non-debug minified versions to reduce bundle size. The Astro Partytown integration should handle this automatically.
| /* Partytown 0.7.6 - MIT builder.io */ | ||
| (e=>{const t=()=>{},r=e=>e.length,n=e=>{var t,r,n;try{const r=null===(t=null==e?void 0:e.constructor)||void 0===t?void 0:t.name;if(r)return r}catch(e){}try{const t=null===(n=null===(r=null==e?void 0:e.__zone_symbol__originalInstance)||void 0===r?void 0:r.constructor)||void 0===n?void 0:n.name;if(t)return t}catch(e){}return""},s=(e,t)=>e.startsWith(t),i=e=>!(s(e,"webkit")||s(e,"toJSON")||s(e,"constructor")||s(e,"toString")||s(e,"_")),o=e=>11===e.nodeType&&e.host?"#s":e.nodeName,a=()=>Math.round(Math.random()*Number.MAX_SAFE_INTEGER).toString(36),l={Anchor:"a",DList:"dl",Image:"img",OList:"ol",Paragraph:"p",Quote:"q",TableCaption:"caption",TableCell:"td",TableCol:"colgroup",TableRow:"tr",TableSection:"tbody",UList:"ul"},c={Graphics:"g",SVG:"svg"},u=Symbol(),h=Symbol(),d=new Map,p=new Map,m={},g=new WeakMap,f=(e,t)=>{if(e)return(t=g.get(e))||(t=e[u])||v(e,t=a()),t},y=(e,t,r,n,s)=>{if((r=m[e])&&r.U){if(e===t)return r.U;if(n=r.U.document,"d"===(s=t.split(".").pop()))return n;if("e"===s)return n.documentElement;if("h"===s)return n.head;if("b"===s)return n.body}return d.get(t)},v=(e,t,r)=>{e&&(d.set(t,e),e[u]=t,e[h]=r=Date.now(),r>w+5e3&&(d.forEach(((e,t)=>{e[h]<w&&e.nodeType&&!e.isConnected&&d.delete(t)})),w=r))};let w=0;const $=e.parent,S=document.implementation.createHTMLDocument(),b=$.partytown||{},E=(b.lib||"/~partytown/")+"",T=(e,t,r)=>{const n=((e,t)=>{return r=e,n={value:t},Object.defineProperty(r,"name",{...n,configurable:!0});var r,n})(class extends m[e].U.HTMLElement{},r[0]);return"connectedCallback,disconnectedCallback,attributeChangedCallback,adoptedCallback".split(",").map((r=>n.prototype[r]=function(...n){t.postMessage([15,e,f(this),r,n])})),n.observedAttributes=r[1],n},C=(e,t,r,i,a)=>void 0!==t&&(i=typeof t)?"string"===i||"number"===i||"boolean"===i||null==t?[0,t]:"function"===i?[6]:(r=r||new Set)&&Array.isArray(t)?r.has(t)?[1,[]]:r.add(t)&&[1,t.map((t=>C(e,t,r)))]:"object"===i?N(t)?[14,{name:t.name,message:t.message,stack:t.stack}]:""===(a=n(t))?[2,{}]:"Window"===a?[3,[e,e]]:"HTMLCollection"===a||"NodeList"===a?[7,Array.from(t).map((t=>C(e,t,r)[1]))]:a.endsWith("Event")?[5,I(e,t,r)]:"CSSRuleList"===a?[12,Array.from(t).map(M)]:s(a,"CSS")&&a.endsWith("Rule")?[11,M(t)]:"CSSStyleDeclaration"===a?[13,I(e,t,r)]:"Attr"===a?[10,[t.name,t.value]]:t.nodeType?[3,[e,f(t),o(t)]]:[2,I(e,t,r,!0,!0)]:void 0:t,I=(e,t,r,s,o,a,l,c)=>{if(a={},!r.has(t))for(l in r.add(t),t)i(l)&&(c="path"===l&&n(t).endsWith("Event")?t.composedPath():t[l],(s||"function"!=typeof c)&&(o||""!==c)&&(a[l]=C(e,c,r)));return a},M=e=>{let t,r={};for(t in e)P.includes(t)&&(r[t]=String(e[t]));return r},N=t=>t instanceof e.top.Error,x=(t,r,n,s)=>r?(n=r[0],s=r[1],0===n?s:4===n?L(t,s):1===n?s.map((e=>x(t,e))):3===n?y(s[0],s[1]):5===n?R(A(t,s)):2===n?A(t,s):8===n?s:9===n?new e[r[2]](s):void 0):void 0,L=(e,{V:t,w:r,L:n},s)=>((s=p.get(n))||(s=function(...s){e.postMessage([9,{V:t,w:r,L:n,R:C(t,this),b:C(t,s)}])},p.set(n,s)),s),R=e=>new("detail"in e?CustomEvent:Event)(e.type,e),A=(e,t,r,n)=>{for(n in r={},t)r[n]=x(e,t[n]);return r},P="cssText,selectorText,href,media,namespaceURI,prefix,name,conditionText".split(","),U=async(e,t)=>{let n,s,i,o,a,l,c={F:t.F},u=r(t.Q),h=0;for(;h<u;h++)try{l=h===u-1,n=t.Q[h],s=n.V,i=n.a,!m[s]&&s.startsWith("f_")&&await new Promise((e=>{let t=0,r=()=>{m[s]||t++>1e3?e():requestAnimationFrame(r)};r()})),1===i[0]&&i[1]in m[s].U?v(new m[s].U[i[1]](...x(e,i[2])),n.w):(o=y(s,n.w),o?(a=j(e,s,o,i,l,n.r),n.d&&("string"==typeof n.d?v(a,n.d):m[n.d.V]={V:n.d.V,U:{document:a}}),"object"==typeof(d=a)&&d&&d.then&&(a=await a,l&&(c.z=!0)),l&&(c.M=C(s,a))):c.p=n.w+" not found")}catch(e){l?c.p=String(e.stack||e):console.error(e)}var d;return c},j=(e,t,s,i,o,a)=>{let l,c,u,h,d,p=0,m=r(i);for(;p<m;p++){c=i[p],l=i[p+1],u=i[p-1];try{if(!Array.isArray(l))if("string"==typeof c||"number"==typeof c){if(p+1===m&&a)return d={},a.map((e=>d[e]=s[e])),d;s=s[c]}else{if(0===l)return void(s[u]=x(e,c));if("function"==typeof s[u]&&(h=x(e,c),"define"===u&&"CustomElementRegistry"===n(s)&&(h[1]=T(t,e,h[1])),"insertRule"===u&&h[1]>r(s.cssRules)&&(h[1]=r(s.cssRules)),s=s[u].apply(s,h),"play"===u))return Promise.resolve()}}catch(e){if(o)throw e;console.debug(e)}}return s},H=(e,t)=>{let n,s,i,o=t.V,a=t.U,l=a.document;l&&l.body?(n=l.querySelector('script[type="text/partytown"]:not([data-ptid]):not([data-pterror]):not([async]):not([defer])'),n||(n=l.querySelector('script[type="text/partytown"]:not([data-ptid]):not([data-pterror])')),n?(n.dataset.ptid=s=f(n,o),i={V:o,w:s},n.src?(i.S=n.src,i.H=n.dataset.ptsrc||n.src):i.j=n.innerHTML,e.postMessage([7,i])):(t.x||(t.x=1,((e,t,n)=>{let s,i,o=n._ptf,a=(n.partytown||{}).forward||[],l=(r,n)=>e.postMessage([10,{V:t,q:r,b:C(t,Array.from(n))}]);if(n._ptf=void 0,a.map((e=>{i=n,e.split(".").map(((e,t,n)=>{i=i[n[t]]=t+1<r(n)?i[n[t]]||("push"===n[t+1]?[]:{}):(...e)=>l(n,e)}))})),o)for(s=0;s<r(o);s+=2)l(o[s],o[s+1])})(e,o,a),l.dispatchEvent(new CustomEvent("pt0"))),e.postMessage([8,o]))):requestAnimationFrame((()=>H(e,t)))},O=(e,t,r)=>{if(!g.has(r)){g.set(r,t);const n=r.document,s=r.history,i=g.get(r.parent);let o=!1;const a=[],l=e=>{o?e():a.push(e)},c=()=>{e.postMessage([5,{V:t,J:i,S:n.baseURI,T:n.visibilityState}]),setTimeout((()=>{o=!0,a.forEach((e=>{e()}))}))},u=s.pushState.bind(s),h=s.replaceState.bind(s),d=(r,s,i,o)=>()=>{setTimeout((()=>{e.postMessage([13,{V:t,type:r,state:s,url:n.baseURI,newUrl:i,oldUrl:o}])}))};s.pushState=(e,t,r)=>{u(e,t,r),l(d(0,e,null==r?void 0:r.toString()))},s.replaceState=(e,t,r)=>{h(e,t,r),l(d(1,e,null==r?void 0:r.toString()))},r.addEventListener("popstate",(e=>{l(d(2,e.state))})),r.addEventListener("hashchange",(e=>{l(d(3,{},e.newURL,e.oldURL))})),r.addEventListener("ptupdate",(()=>{H(e,m[t])})),n.addEventListener("visibilitychange",(()=>e.postMessage([14,t,n.visibilityState]))),m[t]={V:t,U:r},"complete"===n.readyState?c():r.addEventListener("load",c)}},k=()=>{const e=Object.getOwnPropertyNames($).map((e=>((e,t,r,n)=>{if(r=t.match(/^(HTML|SVG)(.+)Element$/))return n=r[2],"S"==t[0]?e.createElementNS("http://www.w3.org/2000/svg",c[n]||n.slice(0,2).toLowerCase()+n.slice(2)):e.createElement(l[n]||n)})(S,e))).filter((e=>e)).map((e=>[e]));return B(e,[])},V=new Set(["Object"]),B=(e,t)=>(e.filter((e=>e[0])).map((e=>{const t=e[0],r=e[1],s=n(t);return[s,$[s].prototype,t,r]})).map((([e,r,n,s])=>D(V,t,e,r,n,s))),t),F=(e,t,r)=>{let n=[],s=[e,"Object",n];for(r in t)W(n,t,r);return s},D=(e,t,r,s,i,a)=>{if(!e.has(r)){e.add(r);const l=Object.getPrototypeOf(s),c=n(l),u=[],h=Object.getOwnPropertyDescriptors(s);D(e,t,c,l,i,a);for(const e in h)W(u,i,e);t.push([r,c,u,a,o(i)])}},W=(e,t,r,s,o,a)=>{try{i(r)&&isNaN(r[0])&&"all"!==r&&("function"==(o=typeof(s=t[r]))?(String(s).includes("[native")||Object.getPrototypeOf(t)[r])&&e.push([r,5]):"object"===o&&null!=s?"Object"!==(a=n(s))&&self[a]&&e.push([r,s.nodeType||a]):"symbol"!==o&&(r.toUpperCase()===r?e.push([r,6,s]):e.push([r,6])))}catch(e){console.warn(e)}},_=e=>{let t,n=[],s=0,i=r($[e]);for(;s<i;s++)t=$[e].key(s),n.push([t,$[e].getItem(t)]);return n},J=(e,r)=>void 0!==e[r]?new e[r](t):0;let q;(async e=>{const t=new SharedArrayBuffer(1073741824),r=new Int32Array(t);return(n,s)=>{const i=s[0],o=s[1];if(0===i){const e=(()=>{const e=S.createElement("i"),t=S.createTextNode(""),r=S.createComment(""),n=S.createDocumentFragment(),s=S.createElement("p").attachShadow({mode:"open"}),i=J($,"IntersectionObserver"),o=J($,"MutationObserver"),a=J($,"ResizeObserver"),l=$.performance,c=$.screen,u=[[$.history],[l],[l.navigation],[l.timing],[c],[c.orientation],[$.visualViewport],[i,12],[o,12],[a,12],[t],[r],[n],[s],[e],[e.attributes],[e.classList],[e.dataset],[e.style],[S],[S.doctype]],h=[F("Window",$),F("Node",t)],d={i:JSON.stringify(b,((e,t)=>("function"==typeof t&&(t=String(t)).startsWith(e+"(")&&(t="function "+t),t))),v:B(u,h),C:new URL(E,$.location)+"",I:origin,D:_("localStorage"),O:_("sessionStorage")};return((e,t,r)=>{void 0!==t[r]&&e.push([r,"Object",Object.keys(t[r].prototype).map((e=>[e,6])),12])})(d.v,$,"IntersectionObserverEntry"),d})();e.P=t,n.postMessage([1,e])}else 2===s[0]?n.postMessage([3,k()]):11===i?e(o,(e=>{const t=JSON.stringify(e),n=t.length;for(let e=0;e<n;e++)r[e+1]=t.charCodeAt(e);r[0]=n,Atomics.notify(r,0)})):((e,t,r)=>{4===t[0]?O(e,a(),$):(r=m[t[1]])&&(7===t[0]?requestAnimationFrame((()=>H(e,r))):6===t[0]&&((e,t,r,n,s)=>{(s=t.U.document.querySelector(`[data-ptid="${r}"]`))&&(n?s.dataset.pterror=n:s.type+="-x",delete s.dataset.ptid),H(e,t)})(e,r,t[2],t[3]))})(n,s)}})(((e,t)=>U(q,e).then(t))).then((e=>{e&&(q=new Worker(URL.createObjectURL(new Blob(['/* Partytown 0.7.6 - MIT builder.io */\n(e=>{const t=Symbol(),r=Symbol(),n=Symbol(),i=Symbol(),s=Symbol(),o=Symbol(),a=Symbol(),l=Symbol(),c=new Map,$={},d=new WeakMap,u=[],h={},p=new Map,g=new Map,m={},f=new Map,w=new Map,y="about:blank",v=e=>e.split(","),S=e=>{if(e=h.C+e,new URL(e).origin!=location.origin)throw"Invalid "+e;return e},I=v("clientWidth,clientHeight,clientTop,clientLeft,innerWidth,innerHeight,offsetWidth,offsetHeight,offsetTop,offsetLeft,outerWidth,outerHeight,pageXOffset,pageYOffset,scrollWidth,scrollHeight,scrollTop,scrollLeft"),b=v("childElementCount,children,firstElementChild,lastElementChild,nextElementSibling,previousElementSibling"),M=v("insertBefore,remove,removeChild,replaceChild"),T=v("className,width,height,hidden,innerHTML,innerText,textContent"),E=v("setAttribute,setAttributeNS,setProperty"),N=v("addEventListener,dispatchEvent,removeEventListener"),x=N.concat(E,v("add,observe,remove,unobserve")),L=/^[A-Z_]([A-Z0-9-]*[A-Z0-9])?$/,C=()=>{},R=e=>e.length,A=e=>{var t,r,n;try{const r=null===(t=null==e?void 0:e.constructor)||void 0===t?void 0:t.name;if(r)return r}catch(e){}try{const t=null===(n=null===(r=null==e?void 0:e.__zone_symbol__originalInstance)||void 0===r?void 0:r.constructor)||void 0===n?void 0:n.name;if(t)return t}catch(e){}return""},W=[],H=()=>Math.round(Math.random()*Number.MAX_SAFE_INTEGER).toString(36),P="text/partytown",O=(e,t,r)=>Object.defineProperty(e,t,{...r,configurable:!0}),j=(e,t)=>O(e,"name",{value:t}),D=(e,t,r)=>O(e.prototype,t,r),k=(e,t)=>Object.defineProperties(e.prototype,t),U=(e,t,r)=>D(e,t,{value:r,writable:!0}),_=(e,t)=>t in e[o],B=(e,t)=>e[o][t],F=(e,t,r)=>e[o][t]=r,V=(e,t,r,n,i)=>(!(i=c.get(t))&&r&&m[e]&&(i=m[e].k(r,t,n),c.set(t,i)),i),z=(e,t)=>U(e,"nodeType",t),G=(e,t)=>t.map((t=>D(e,t,{get(){let e=X(this,t),r=w.get(e);return r||(r=de(this,[t]),w.set(e,r)),r}}))),X=(e,n,i)=>[e[t],e[r],n,...(i||W).map((e=>String(e&&e[t]?e[r]:e)))].join("."),q=(e,t)=>v(t).map((t=>D(e,t,{get(){return _(this,t)||F(this,t,de(this,[t])),B(this,t)},set(e){B(this,t)!==e&&ue(this,[t],e),F(this,t,e)}}))),Z=e=>I.map((t=>D(e,t,{get(){const e=f.get(X(this,t));if("number"==typeof e)return e;const r=de(this,[t],I);return r&&"object"==typeof r?(Object.entries(r).map((([e,t])=>f.set(X(this,e),t))),r[t]):r}}))),J=(e,t)=>t.map((t=>{e.prototype[t]=function(...e){let r=X(this,t,e),n=f.get(r);return n||(n=he(this,[t],e),f.set(r,n)),n}})),Y=(e,n,i,s,o)=>{return void 0!==i&&(o=typeof i)?"string"===o||"boolean"===o||"number"===o||null==i?[0,i]:"function"===o?[4,{V:e,w:n,L:(a=i,(l=d.get(a))||(d.set(a,l=H()),$[l]=a),l)}]:(s=s||new Set)&&Array.isArray(i)?s.has(i)?[1,[]]:s.add(i)&&[1,i.map((t=>Y(e,n,t,s)))]:"object"===o?i[r]?[3,[i[t],i[r]]]:i instanceof Event?[5,Q(e,n,i,!1,s)]:K&&i instanceof TrustedHTML?[0,i.toString()]:i instanceof ArrayBuffer?[8,i]:ArrayBuffer.isView(i)?[9,i.buffer,A(i)]:[2,Q(e,n,i,!0,s)]:void 0:i;var a,l},K="undefined"!=typeof TrustedHTML,Q=(e,t,r,n,i,s,o,a)=>{if(s={},!i.has(r))for(o in i.add(r),r)a=r[o],(n||"function"!=typeof a)&&(s[o]=Y(e,t,a,i));return s},ee=(e,n)=>e?Y(e[t],e[r],n):[0,n],te=(e,t,r,n,i,s,o,a)=>{if(n){if(i=n[0],s=n[1],0===i||11===i||12===i)return s;if(4===i)return ne(r,s);if(6===i)return e&&r.length>0?(...t)=>he(m[e].U,r,t,1):C;if(3===i)return re(s);if(7===i)return new se(s.map(re));if(10===i)return new oe(s);if(1===i)return s.map((n=>te(e,t,r,n)));if(14===i)return new ie(s);for(a in o={},s)o[a]=te(e,t,[...r,a],s[a]);if(13===i)return new m[e].U.CSSStyleDeclaration(e,t,r,o);if(5===i){if("message"===o.type&&o.origin){let e,t=JSON.stringify(o.data),r=u.find((e=>e.m===t));r&&(e=m[r.V],e&&(o.source=e.U,o.origin=e.E.origin))}return new Proxy(new Event(o.type,o),{get:(e,t)=>t in o?o[t]:"function"==typeof e[String(t)]?C:e[String(t)]})}if(2===i)return o}},re=([e,t,r])=>t===e&&m[e]?m[e].U:V(e,t,r),ne=(e,{V:t,w:r,G:n,L:i})=>($[i]||d.set($[i]=function(...i){const s=V(t,r,n);return he(s,e,i)},i),$[i]);class ie extends Error{constructor(e){super(e.message),this.name=e.name,this.message=e.message,this.stack=e.stack}}class se{constructor(e){(this._=e).map(((e,t)=>this[t]=e))}entries(){return this._.entries()}forEach(e,t){this._.map(e,t)}item(e){return this[e]}keys(){return this._.keys()}get length(){return R(this._)}values(){return this._.values()}[Symbol.iterator](){return this._[Symbol.iterator]()}}const oe=class{constructor(e){this.name=e[0],this.value=e[1]}get nodeName(){return this.name}get nodeType(){return 2}},ae=(e,t,r)=>console.warn(`Partytown unable to ${e} cross-origin ${t}: `+r.E),le=[],ce=(e,n,i,o,a,l)=>{if(e[s]){if(le.push({V:e[t],w:e[r],a:[...e[s],...n],d:o,r:a}),3===i)h.K([12,{F:H(),Q:[...le]}],l?[l instanceof ArrayBuffer?l:l.buffer]:void 0),le.length=0;else if(1===i)return $e(!0);h.c=setTimeout($e,20)}},$e=e=>{if(clearTimeout(h.c),R(le)){const t=le[R(le)-1],r={F:H(),Q:[...le]};if(le.length=0,e){const e=((e,t)=>{const r=e.P,n=new Int32Array(r);Atomics.store(n,0,0),e.K([11,t]),Atomics.wait(n,0,0);let i=Atomics.load(n,0),s="",o=0;for(;o<i;o++)s+=String.fromCharCode(n[o+1]);return JSON.parse(s)})(h,r),n=e.z,i=te(t.V,t.w,t.a,e.M);if(e.p){if(n)return Promise.reject(e.p);throw new Error(e.p)}return n?Promise.resolve(i):i}h.K([12,r])}},de=(e,t,r,n)=>h.i.get&&(n=h.i.get(ge(e,t)))!==a?n:n=ce(e,t,1,void 0,r),ue=(e,t,r,n)=>{if(h.i.set){if((n=h.i.set({value:r,prevent:l,...ge(e,t)}))===l)return;n!==a&&(r=n)}T.some((e=>t.includes(e)))&&(f.clear(),t[t.length-1]),t=[...t,ee(e,r),0],ce(e,t,2)},he=(e,t,r,n,i,s,o,l)=>h.i.apply&&(o=h.i.apply({args:r,...ge(e,t)}))!==a?o:(l=t[R(t)-1],t=[...t,ee(e,r)],n=n||(x.includes(l)?2:1),"setAttribute"===l&&_(e,r[0])?F(e,r[0],r[1]):M.includes(l)?(f.clear(),w.clear()):E.includes(l)&&(n=2,f.clear()),o=ce(e,t,n,i,void 0,s)),pe=(e,t,r)=>{ce(e,[1,t,ee(e,r)],1)},ge=(e,r)=>({name:r.join("."),continue:a,nodeName:e[n],constructor:A(e),instance:e,window:m[e[t]].U}),me=(e,t,r,n,i)=>{let s,o,a=t=>((t=r.get(e.origin))||r.set(e.origin,t=[]),t),l=e=>a().findIndex((t=>t[fe]===e)),c={getItem:e=>(s=l(e),s>-1?a()[s][we]:null),setItem(r,o){s=l(r),s>-1?a()[s][we]=o:a().push([r,o]),n?he(e,[t,"setItem"],[r,o],2):ae("set",t,i)},removeItem(r){s=l(r),s>-1&&a().splice(s,1),n?he(e,[t,"removeItem"],[r],2):ae("remove",t,i)},key:e=>(o=a()[e],o?o[fe]:null),clear(){a().length=0,n?he(e,[t,"clear"],W,2):ae("clear",t,i)},get length(){return a().length}};e[t]=new Proxy(c,{get:(e,t)=>Reflect.has(e,t)?Reflect.get(e,t):e.getItem(t),set:(e,t,r)=>(e.setItem(t,r),!0),has:(e,t)=>!!Reflect.has(e,t)||"string"==typeof t&&null!==e.getItem(t),deleteProperty:(e,t)=>(e.removeItem(t),!0)})},fe=0,we=1,ye=(e,t,r)=>{e[r]=j(class extends t{constructor(e,t,r,i){return super(e,t,r,i||{}),new Proxy(this,{get:(e,t)=>e[t]?e[t]:(e[t]||"string"!=typeof t||e[n][t]||(e[n][t]=de(e,[t])),e[n][t]),set:(e,t,r)=>(e[n][t]=r,ue(e,[t],r),f.clear(),!0)})}setProperty(...e){this[n][e[0]]=e[1],he(this,["setProperty"],e,2),e[0],f.clear()}getPropertyValue(e){return this[e]}removeProperty(e){let t=this[n][e];return he(this,["removeProperty"],[e],2),f.clear(),this[n][e]=void 0,t}},r)},ve=(e,t)=>{e[t]=j(class{constructor(e){this.ownerNode=e}get cssRules(){const e=this.ownerNode;return new Proxy({},{get(t,r){const n=String(r);return"item"===n?t=>Ie(e,t):"length"===n?Se(e).length:isNaN(n)?t[r]:Ie(e,n)}})}insertRule(e,t){const r=Se(this.ownerNode);return(t=void 0===t?0:t)>=0&&t<=r.length&&(he(this.ownerNode,["sheet","insertRule"],[e,t],2),r.splice(t,0,0)),this.ownerNode,f.clear(),t}deleteRule(e){he(this.ownerNode,["sheet","deleteRule"],[e],2),Se(this.ownerNode).splice(e,1),this.ownerNode,f.clear()}get type(){return"text/css"}},t);const r={sheet:{get(){return new e[t](this)}}};k(e.HTMLStyleElement,r)},Se=(e,t)=>((t=B(e,2))||(t=de(e,["sheet","cssRules"]),F(e,2,t)),t),Ie=(e,t,r)=>(0===(r=Se(e))[t]&&(r[t]=de(e,["sheet","cssRules",parseInt(t,10)])),r[t]),be="0.7.6",Me=(e,t,r,n,i)=>{try{e.l=t,Te(e,r)}catch(e){console.error(r,e),i=String(e.stack||e)}return e.l="",i},Te=(e,t,r)=>{e.N=1,t=`with(this){${t.replace(/\\bthis\\b/g,((e,t,r)=>t>0&&"$"!==r[t-1]?"(thi$(this)?window:this)":e)).replace(/\\/\\/# so/g,"//Xso")}\\n;function thi$(t){return t===this}};${(h.i.globalFns||[]).filter((e=>/[a-zA-Z_$][0-9a-zA-Z_$]*/.test(e))).map((e=>`(typeof ${e}==\'function\'&&(this.${e}=${e}))`)).join(";")};`+(r?"\\n//# sourceURL="+r:""),e.A||(t=t.replace(/.postMessage\\(/g,`.postMessage(\'${e.V}\',`)),new Function(t).call(e.U),e.N=0},Ee=(e,t,r)=>{(r=B(e,t))&&setTimeout((()=>r.map((e=>e({type:t})))))},Ne=(e,t,r,n,i,s)=>{for(n=e.E;!n.host&&(n=(e=m[e.J]).E,e.V!==e.J););return i=new URL(t||"",n),r&&h.i.resolveUrl&&(s=h.i.resolveUrl(i,n,r))?s:i},xe=(e,t,r)=>Ne(e,t,r)+"",Le=()=>`<script src="${S("partytown.js?v="+be)}"><\\/script>`,Ce=e=>class{constructor(){this.s="",this.l=[],this.e=[],this.style={}}get src(){return this.s}set src(t){this.s=t,fetch(xe(e,t,"image"),{mode:"no-cors",credentials:"include",keepalive:!0}).then((e=>{e.ok||0===e.status?this.l.map((e=>e({type:"load"}))):this.e.map((e=>e({type:"error"})))}),(()=>this.e.forEach((e=>e({type:"error"})))))}addEventListener(e,t){"load"===e&&this.l.push(t),"error"===e&&this.e.push(t)}get onload(){return this.l[0]}set onload(e){this.l=[e]}get onerror(){return this.e[0]}set onerror(e){this.e=[e]}},Re={addEventListener:{value(...e){const t=e[0],r=B(this,t)||[];r.push(e[1]),F(this,t,r)}},async:{get:C,set:C},defer:{get:C,set:C},onload:{get(){let e=B(this,"load");return e&&e[0]||null},set(e){F(this,"load",e?[e]:null)}},onerror:{get(){let e=B(this,"error");return e&&e[0]||null},set(e){F(this,"error",e?[e]:null)}},getAttribute:{value(e){return"src"===e?this.src:he(this,["getAttribute"],[e])}},setAttribute:{value(e,t){Ae.includes(e)?this[e]=t:he(this,["setAttribute"],[e,t])}}},Ae=v("src,type"),We=(e,t)=>{const r={innerHTML:He,innerText:He,src:{get(){return B(this,4)||""},set(e){const r=xe(t,e,null),n=h.i;e=xe(t,e,"script"),F(this,4,e),ue(this,["src"],e),r!==e&&ue(this,["dataset","ptsrc"],r),this.type&&n.loadScriptsOnMainThread&&n.loadScriptsOnMainThread.some((t=>t===e))&&ue(this,["type"],"text/javascript")}},textContent:He,type:{get(){return de(this,["type"])},set(e){Pe(e)||(F(this,5,e),ue(this,["type"],e))}},...Re};k(e,r)},He={get(){const e=de(this,["type"]);return Pe(e)?B(this,3)||"":de(this,["innerHTML"])},set(e){F(this,3,e)}},Pe=e=>!e||"text/javascript"===e,Oe=(e,i,s)=>{const o=h.i,a=j(class extends s{appendChild(e){return this.insertBefore(e,null)}get href(){}set href(e){}insertBefore(e,s){var a,l;const c=e[t]=this[t],$=e[r],d=e[n],u="SCRIPT"===d,p="IFRAME"===d;if(u){const t=B(e,3),r=B(e,5);if(t){if(Pe(r)){const r=e.id;if(r&&(null===(l=null===(a=o.loadScriptsOnMainThread)||void 0===a?void 0:a.includes)||void 0===l?void 0:l.call(a,r)))ue(e,["type"],"text/javascript");else{const r=Me(i,$,t,0,""),n=r?"pterror":"ptid",s=r||$;ue(e,["type"],P+"-x"),ue(e,["dataset",n],s)}}ue(e,["innerHTML"],t)}}if(he(this,["insertBefore"],[e,s],2),p){const t=B(e,0);if(t&&t.startsWith("javascript:")){const e=t.split("javascript:")[1];Me(i,$,e,0,"")}((e,t)=>{let r,n,i=0,s=()=>{m[e]&&m[e].x&&!m[e].y?(r=B(t,1)?"error":"load",n=B(t,r),n&&n.map((e=>e({type:r})))):i++>2e3?(n=B(t,"error"),n&&n.map((e=>e({type:"error"})))):setTimeout(s,9)};s()})($,e)}return u&&($e(!0),h.K([7,c])),e}get nodeName(){return"#s"===this[n]?"#document-fragment":this[n]}get nodeType(){return 3}get ownerDocument(){return i.n}},"Node");G(a,v("childNodes,firstChild,isConnected,lastChild,nextSibling,parentElement,parentNode,previousSibling")),e.Node=a},je=v("AUDIO,CANVAS,VIDEO"),De=v("Audio,MediaSource"),ke=(e,r,n)=>{k(e,{body:{get:()=>r.e},cookie:{get(){return r.A?de(this,["cookie"]):(ae("get","cookie",r),"")},set(e){r.A&&ue(this,["cookie"],e)}},createElement:{value(e){if(e=e.toUpperCase(),!L.test(e))throw e+" not valid";const r="IFRAME"===e,n=this[t],i=(r?"f_":"")+H();he(this,["createElement"],[e],2,i);const s=V(n,i,e);if(r)et({V:i,J:n,S:y},!0).U.fetch=fetch,ue(s,["srcdoc"],Le());else if("SCRIPT"===e){const e=B(s,5);Pe(e)&&ue(s,["type"],P)}return s}},createElementNS:{value(e,r){const n=H(),i=V(this[t],n,r,e);return he(this,["createElementNS"],[e,r],2,n),i}},createTextNode:{value(e){const r=this[t],n=H(),i=V(r,n,"#text");return he(this,["createTextNode"],[e],2,n),i}},createEvent:{value:e=>new Event(e)},currentScript:{get(){return r.l?V(this[t],r.l,"SCRIPT"):null}},defaultView:{get:()=>n?null:r.U},documentElement:{get:()=>r.o},getElementsByTagName:{value(e){return"BODY"===(e=e.toUpperCase())?[r.e]:"HEAD"===e?[r.s]:he(this,["getElementsByTagName"],[e])}},head:{get:()=>r.s},images:{get(){return de(this,["images"])}},implementation:{get(){return{hasFeature:()=>!0,createHTMLDocument:e=>{const t=H();return he(this,["implementation","createHTMLDocument"],[e],1,{V:t}),et({V:t,J:t,S:r.E+"",T:"hidden"},!0,!0).n}}}},location:{get:()=>r.E,set(e){r.E.href=e+""}},nodeType:{value:9},parentNode:{value:null},parentElement:{value:null},readyState:{value:"complete"},visibilityState:{get:()=>r.T||"visible"}}),q(e,"compatMode,referrer,forms")},Ue=(e,t)=>{k(e,{parentElement:{get(){return this.parentNode}},parentNode:{get:()=>t.o}})},_e=(e,t)=>{k(e,{localName:{get(){return this[n].toLowerCase()}},namespaceURI:{get(){return this[i]||"http://www.w3.org/1999/xhtml"}},nodeType:{value:1},tagName:{get(){return this[n]}}}),G(e,b),q(e,"id"),Z(t),J(t,v("getClientRects,getBoundingClientRect"))},Be=(e,t)=>{const r={};v("hash,host,hostname,href,origin,pathname,port,protocol,search").map((e=>{r[e]={get(){let r,n=B(this,4);return"string"!=typeof n&&(r=de(this,["href"]),F(this,4,r),n=new URL(r)[e]),Ne(t,n,null)[e]},set(r){let n;if("href"===e)if((e=>{try{return new URL(e),!0}catch(e){return!1}})(r))n=new URL(r);else{const e=t.E.href;n=Ne(t,e,null),n.href=new URL(r+"",n.href)}else n=Ne(t,this.href,null),n[e]=r;F(this,4,n.href),ue(this,["href"],n.href)}}})),k(e,r)},Fe=(e,t)=>{const r={contentDocument:{get(){return Xe(this).n}},contentWindow:{get(){return Xe(this).U}},src:{get(){let e=B(this,0);return e&&e.startsWith("javascript:")?e:(e=Xe(this).E.href,e.startsWith("about:")?"":e)},set(e){if(e)if(e.startsWith("javascript:"))F(this,0,e);else if(!e.startsWith("about:")){let t,r=new XMLHttpRequest,n=Xe(this);n.E.href=e=xe(n,e,"iframe"),n.y=1,F(this,1,void 0),r.open("GET",e,!1),r.send(),t=r.status,t>199&&t<300?(ue(this,["srcdoc"],`<base href="${e}">`+r.responseText.replace(ze,((e,t)=>{const r=[];let n,i=!1;for(;n=Ge.exec(t);){let[e]=n;e.startsWith("type=")&&(i=!0,e=e.replace(/(application|text)\\/javascript/,P)),r.push(e)}return i||r.push(\'type="text/partytown"\'),`<script ${r.join(" ")}>`}))+Le()),$e(!0),h.K([7,n.V])):(F(this,1,t),n.y=0)}}},...Re};k(e,r)},Ve="((?:\\\\w|-)+(?:=(?:(?:\\\\w|-)+|\'[^\']*\'|\\"[^\\"]*\\")?)?)",ze=new RegExp(`<script\\\\s*((${Ve}\\\\s*)*)>`,"mg"),Ge=new RegExp(Ve,"mg"),Xe=e=>{const n=e[r];return m[n]||et({V:n,J:e[t],S:de(e,["src"])||y},!0),m[n]},qe=e=>{const t=(e,t)=>{const{a:r,b:n,c:i,d:s,e:o,f:a}=he(e,[t],W);return new DOMMatrixReadOnly([r,n,i,s,o,a])},r={...e,getCTM:{value:function(){return t(this,"getCTM")}},getScreenCTM:{value:function(){return t(this,"getScreenCTM")}}};k(e,r)},Ze=(e,t)=>{e.NamedNodeMap=j(class extends t{constructor(e,t,r){return super(e,t,r),new Proxy(this,{get(e,t){const r=Ye[t];return r?r.bind(e,[t]):de(e,[t])},set(e,t,r){if(Ye[t])throw new Error("Can\'t set read-only property: "+String(t));return ue(e,[t],r),!0}})}},"NamedNodeMap")};function Je(e,...t){return he(this,e,t,1)}const Ye={getNamedItem:Je,getNamedItemNS:Je,item:Je,removeNamedItem:Je,removeNamedItemNS:Je,setNamedItem:Je,setNamedItemNS:Je},Ke=(a,l,c,$,d,f)=>{let w,I,M;const T=class{constructor(e,l,c,$,d){this[t]=e||a,this[r]=l||w||H(),this[s]=c||[],this[n]=$||I,this[i]=d||M,this[o]={},w=I=M=void 0}},E=new(j(class extends URL{assign(){}reload(){}replace(){}},"Location"))(c),x=E.origin===h.I||E.origin===y,L=l===a,C={},W=()=>{let e,t,r=[];for(e in m)t=m[e],t.J!==a||t.B||r.push(t);return r},P=j(class extends T{constructor(){super(a,a);let n,i,o=this,c=0,u=()=>{c||((h.u||(e.g=[de,ue,he,pe,k,H,t,r,s],h.t(S("partytown-media.js?v="+be)),h.u=e.f,delete e.f),h.u)(T,Y,C,o,De),c=1)},m={},y=(e,t,r)=>{je.includes(e)&&u();const n=m[e]?m[e]:e.includes("-")?m.UNKNOWN:m.I;return w=t,I=e,M=r,new n};var N,R;if(o.Window=P,o.name=name+a,Oe(o,C,T),(e=>{e.NodeList=j(se,"NodeList")})(o),Ze(o,T),ye(o,T,"CSSStyleDeclaration"),((e,t,r)=>{e[r]=j(class extends t{now(){return performance.now()}},r)})(o,T,"Performance"),((e,t)=>{const r="customElements",n=new Map;e.customElements={define(i,s,o){n.set(i,s),t[i.toUpperCase()]=s;const a=[s.name,s.observedAttributes];he(e,[r,"define"],[i,a,o])},get:t=>n.get(t)||he(e,[r,"get"],[t]),whenDefined:t=>n.has(t)?Promise.resolve():he(e,[r,"whenDefined"],[t]),upgrade:t=>he(e,[r,"upgrade"],[t])}})(o,m),h.v.map((([e,t,n,i,l])=>{const c=Qe[e]?X:"EventTarget"===t?Y:"Object"===t?T:o[t],$=o[e]=j(12===i?class extends T{constructor(...t){super(),pe(this,e,t)}}:o[e]||class extends c{},e);l&&(m[l]=$),n.map((([e,t,n])=>{e in $.prototype||e in c.prototype||("string"==typeof t?D($,e,{get(){if(!_(this,e)){const n=this[r],i=[...this[s],e],l=o[t];l&&F(this,e,new l(a,n,i))}return B(this,e)},set(t){F(this,e,t)}}):5===t?U($,e,(function(...t){return he(this,[e],t)})):t>0&&(void 0!==n?U($,e,n):D($,e,{get(){return de(this,[e])},set(t){return ue(this,[e],t)}})))}))})),v("atob,btoa,crypto,indexedDB,setTimeout,setInterval,clearTimeout,clearInterval").map((t=>{delete P.prototype[t],t in o||(n=e[t],null!=n&&(o[t]="function"!=typeof n||n.toString().startsWith("class")?n:n.bind(e)))})),Object.getOwnPropertyNames(e).map((t=>{t in o||(o[t]=e[t])})),De.map((e=>O(o,e,{get:()=>(u(),o[e])}))),"trustedTypes"in e&&(o.trustedTypes=e.trustedTypes),_e(o.Element,o.HTMLElement),ke(o.Document,C,f),N=o.DocumentFragment,z(N,11),G(N,b),Be(o.HTMLAnchorElement,C),R=o.HTMLFormElement,k(R,{}),q(R,"elements"),Fe(o.HTMLIFrameElement),We(o.HTMLScriptElement,C),qe(o.SVGGraphicsElement),Ue(o.HTMLHeadElement,C),Ue(o.HTMLBodyElement,C),((e,t)=>{k(e,{parentElement:{value:null},parentNode:{get:()=>t.n}})})(o.HTMLHtmlElement,C),ve(o,"CSSStyleSheet"),z(o.Comment,8),z(o.DocumentType,10),Object.assign(C,{V:a,J:l,U:new Proxy(o,{get:(e,t)=>{var r;if("string"!=typeof t||isNaN(t))return(null===(r=h.i.mainWindowAccessors)||void 0===r?void 0:r.includes(t))?de(this,[t]):e[t];{let e=W()[t];return e?e.U:void 0}},has:()=>!0}),n:y("#document",a+".d"),o:y("HTML",a+".e"),s:y("HEAD",a+".h"),e:y("BODY",a+".b"),E:E,T:$,A:x,B:L,k:y}),o.requestAnimationFrame=e=>setTimeout((()=>e(performance.now())),9),o.cancelAnimationFrame=e=>clearTimeout(e),o.requestIdleCallback=(e,t)=>(t=Date.now(),setTimeout((()=>e({didTimeout:!1,timeRemaining:()=>Math.max(0,50-(Date.now()-t))})),1)),o.cancelIdleCallback=e=>clearTimeout(e),me(o,"localStorage",p,x,C),me(o,"sessionStorage",g,x,C),x||(o.indexeddb=void 0),d)i={},o.history={pushState(e){i=e},replaceState(e){i=e},get state(){return i},length:0},o.indexeddb=void 0;else{const e=o.history.pushState.bind(o.history),t=o.history.replaceState.bind(o.history);o.history.pushState=(t,r,n)=>{!1!==C.$propagateHistoryChange$&&e(t,r,n)},o.history.replaceState=(e,r,n)=>{!1!==C.$propagateHistoryChange$&&t(e,r,n)}}o.Worker=void 0}addEventListener(...e){"load"===e[0]?C.N&&setTimeout((()=>e[1]({type:"load"}))):he(this,["addEventListener"],e,2)}get body(){return C.e}get document(){return C.n}get documentElement(){return C.o}fetch(e,t){return e="string"==typeof e||e instanceof URL?String(e):e.url,fetch(xe(C,e,"fetch"),t)}get frames(){return C.U}get frameElement(){return L?null:V(l,a,"IFRAME")}get globalThis(){return C.U}get head(){return C.s}get length(){return W().length}get location(){return E}set location(e){E.href=e+""}get Image(){return Ce(C)}get navigator(){return(e=>{let t,r={sendBeacon:(t,r)=>{try{return fetch(xe(e,t,null),{method:"POST",body:r,mode:"no-cors",keepalive:!0}),!0}catch(e){return console.error(e),!1}}};for(t in navigator)r[t]=navigator[t];return new Proxy(r,{set:(e,t,r)=>(navigator[t]=r,!0)})})(C)}get origin(){return E.origin}set origin(e){}get parent(){for(let e in m)if(m[e].V===l)return m[e].U;return C.U}postMessage(...e){m[e[0]]&&(R(u)>50&&u.splice(0,5),u.push({V:e[0],m:JSON.stringify(e[1])}),e=e.slice(1)),he(this,["postMessage"],e,3)}get self(){return C.U}get top(){for(let e in m)if(m[e].B)return m[e].U;return C.U}get window(){return C.U}get XMLHttpRequest(){const e=XMLHttpRequest,t=String(e),r=j(class extends e{open(...e){e[1]=xe(C,e[1],"xhr"),super.open(...e)}set withCredentials(e){}toString(){return t}},A(e));return r.prototype.constructor.toString=()=>t,r}},"Window"),X=class extends T{constructor(e,t,r,n){return super(e,t,r,n),new Proxy(this,{get:(e,t)=>de(e,[t]),set:(e,t,r)=>(ue(e,[t],r),!0)})}},Y=class extends T{};return N.map((e=>Y.prototype[e]=function(...t){return he(this,[e],t,2)})),q(P,"devicePixelRatio"),Z(P),J(P,["getComputedStyle"]),new P,C},Qe={DOMStringMap:1,NamedNodeMap:1},et=({V:e,J:t,S:r,T:n},i,s)=>(m[e]||(m[e]=Ke(e,t,r,n,i,s)),h.K([7,e]),m[e]),tt=[],rt=t=>{const r=t.data,n=r[0],i=r[1];if(h.x)if(7===n)(async e=>{let t,r=e.V,n=e.w,i=V(r,n,"SCRIPT"),s=e.j,o=e.S,a=e.H,l="",c=m[r],$=["text/jscript","text/javascript","text/x-javascript","application/javascript","application/x-javascript","text/ecmascript","text/x-ecmascript","application/ecmascript"];if(o)try{if(o=Ne(c,o,"script")+"",F(i,4,o),t=await fetch(o),t.ok){let e=t.headers.get("content-type");$.some((t=>{var r,n,i;return null===(i=null===(r=null==e?void 0:e.toLowerCase)||void 0===r?void 0:(n=r.call(e)).includes)||void 0===i?void 0:i.call(n,t)}))&&(s=await t.text(),c.l=n,Te(c,s,a||o)),Ee(i,"load")}else l=t.statusText,Ee(i,"error")}catch(e){console.error(e),l=String(e.stack||e),Ee(i,"error")}else s&&(l=Me(c,n,s,0,l));c.l="",h.K([6,r,n,l])})(i);else if(9===n)(({V:e,w:t,L:r,R:n,b:i})=>{if($[r])try{$[r].apply(te(e,t,[],n),te(e,t,[],i))}catch(e){console.error(e)}})(i);else if(10===n)(({V:e,q:t,b:r})=>{try{let n=m[e].U,i=0,s=R(t);for(;i<s;i++)i+1<s?n=n[t[i]]:n[t[i]].apply(n,te(null,e,[],r))}catch(e){console.error(e)}})(i);else if(5===n)et(i);else if(8===n)m[i].x=1,m[i].y=0;else if(14===n)m[i].T=r[2];else if(13===n){const e=i.V,t=m[e];t.E.href=i.url,function(e,t,r){const n=t.U.history;switch(r.type){case 0:t.$propagateHistoryChange$=!1;try{n.pushState(r.state,"",r.newUrl)}catch(e){}t.$propagateHistoryChange$=!0;break;case 1:t.$propagateHistoryChange$=!1;try{n.replaceState(r.state,"",r.newUrl)}catch(e){}t.$propagateHistoryChange$=!0}}(i.V,t,i)}else 15===n&&((e,t,r,n,i)=>{const s=V(t,r);s&&"function"==typeof s[n]&&s[n].apply(s,i)})(...r);else 1===n?((t=>{const r=h.i=JSON.parse(t.i),n=t.I;h.t=importScripts.bind(e),h.v=t.v,h.C=t.C,h.I=n,h.K=postMessage.bind(e),h.P=t.P,p.set(n,t.D),g.set(n,t.O),e.importScripts=void 0,delete e.postMessage,delete e.WorkerGlobalScope,v("resolveUrl,get,set,apply").map((e=>{r[e]&&(r[e]=new Function("return "+r[e])())}))})(i),h.K([2])):3===n?(h.v=[...h.v,...i],h.x=1,h.K([4]),[...tt].map(rt),tt.length=0):tt.push(t)};e.onmessage=rt,postMessage([0])})(self);\n'],{type:"text/javascript"})),{name:"Partytown 🎉"}),q.onmessage=t=>{const r=t.data;12===r[0]?U(q,r[1]):e(q,r)},$.addEventListener("pt1",(e=>O(q,f(e.detail.frameElement),e.detail))))}))})(window); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Partytown latest version 2025 builder.io copylib
💡 Result:
Short answer:
- As of Jan 1, 2026, the original @builder.io/partytown package is deprecated (latest published 0.10.3) — the actively maintained package is now @qwik.dev/partytown. [1][2]
- To copy the Partytown lib files you can use the built-in CLI task: partytown copylib , e.g. npx partytown copylib public/~partytown. (Docs show the same copylib/“copy library files” guidance and example npm script.) [3][4]
Sources
- npm package page for @builder.io/partytown (shows deprecation and latest version). [npm @builder.io/partytown]. [2]
- Migration / community notes indicating the package moved under @qwik.dev (recommend using @qwik.dev/partytown for latest). [syntackle / QwikDev notes]. [6]
- Partytown docs — “Copy Library Files” and copylib CLI usage (example: partytown copylib public/~partytown). [partytown docs]. [4]
- Builder/Partytown overview (maintenance by Builder.io / project home). [partytown site / Builder blog]. [5]
Would you like me to fetch the exact latest @qwik.dev/partytown version now and show the npm install command?
Hey, about those Partytown files — there's something important you should know.
This is Partytown 0.7.6, and here's the deal: that version is pretty dated. The original @builder.io/partytown package is now deprecated, and the actively maintained version lives over at @qwik.dev/partytown. You'll definitely want to consider upgrading, especially if you're setting up a new project or maintaining this long-term.
The good news? The copylib script recommendation still stands — that's genuinely the right move. You can use partytown copylib public/~partytown (or whatever your destination is) to pull in the latest files from the maintained package. It beats committing minified vendor code directly and makes updates way smoother down the line.
If you're already invested in 0.7.6 and it's working fine for your use case, you're not forced to upgrade tomorrow. But I'd definitely recommend checking out the @qwik.dev/partytown docs and planning a migration when it makes sense. The project's in good hands over there, and you'll get better support and updates going forward.
🧰 Tools
🪛 Biome (2.1.2)
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
[error] 2-2: Can't assign d because it's a constant.
This is where the variable is defined as constant.
Unsafe fix: Replace const with let if you assign it to a new value.
(lint/correctness/noConstAssign)
🤖 Prompt for AI Agents
In public/~partytown/partytown-atomics.js lines 1-2, the project has committed a
dated, minified Partytown vendor file (Partytown 0.7.6) which is deprecated;
replace this workflow by removing the committed minified files and adopting the
maintained package (@qwik.dev/partytown) and the copylib approach: add
@qwik.dev/partytown as a dev dependency (or update any existing partytown
package), add a script (e.g., "partytown:copy": "partytown copylib
public/~partytown") to package.json, run that to populate public/~partytown
during install/build, and remove the checked-in minified files from the repo so
future updates come from the maintained package.
| /* Partytown 0.7.6 - MIT builder.io */ | ||
| !function(t,e,n,i,r,o,a,d,s,c,p,l){function u(){l||(l=1,"/"==(a=(o.lib||"/~partytown/")+(o.debug?"debug/":""))[0]&&(s=e.querySelectorAll('script[type="text/partytown"]'),i!=t?i.dispatchEvent(new CustomEvent("pt1",{detail:t})):(d=setTimeout(f,1e4),e.addEventListener("pt0",w),r?h(1):n.serviceWorker?n.serviceWorker.register(a+(o.swPath||"partytown-sw.js"),{scope:a}).then((function(t){t.active?h():t.installing&&t.installing.addEventListener("statechange",(function(t){"activated"==t.target.state&&h()}))}),console.error):f())))}function h(t){c=e.createElement(t?"script":"iframe"),t||(c.setAttribute("style","display:block;width:0;height:0;border:0;visibility:hidden"),c.setAttribute("aria-hidden",!0)),c.src=a+"partytown-"+(t?"atomics.js?v=0.7.6":"sandbox-sw.html?"+Date.now()),e.body.appendChild(c)}function f(n,r){for(w(),i==t&&(o.forward||[]).map((function(e){delete t[e.split(".")[0]]})),n=0;n<s.length;n++)(r=e.createElement("script")).innerHTML=s[n].innerHTML,e.head.appendChild(r);c&&c.parentNode.removeChild(c)}function w(){clearTimeout(d)}o=t.partytown||{},i==t&&(o.forward||[]).map((function(e){p=t,e.split(".").map((function(e,n,i){p=p[i[n]]=n+1<i.length?"push"==i[n+1]?[]:p[i[n]]||{}:function(){(t._ptf=t._ptf||[]).push(i,arguments)}}))})),"complete"==e.readyState?u():(t.addEventListener("DOMContentLoaded",u),t.addEventListener("load",u))}(window,document,navigator,top,window.crossOriginIsolated); No newline at end of file |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the latest version of @builder.io/partytown on npm
curl -s https://registry.npmjs.org/@builder.io/partytown/latest | jq '{version: .version, published: .time.created}'Repository: aravindputrevu/aravind.dev
Length of output: 116
🌐 Web query:
Partytown 0.7.6 release notes builder.io latest version
💡 Result:
-
Partytown 0.7.6 — package files available (release tag present on CDN/package indexes). See @builder.io/partytown@0.7.6 on the public CDN listing. [1]
-
Partytown repo (official) — releases list (latest release shown on GitHub: v0.11.2, Jul 16, 2025). For full changelog and per‑tag release notes consult the repository Releases/CHANGELOG on GitHub. [2]
-
Builder.io — current SDK/package versions (examples):
- Builder repo releases page (recent SDK/react package releases). [3]
- @builder.io/react package on Yarn / registry showing latest published version (example: 9.1.0 published Dec 17, 2025). For Builder's Content API the docs recommend v3 as the latest API version. [4][5]
Sources
- @builder.io/partytown package listing (CDN/jsDelivr) — shows @builder.io/partytown@0.7.6. [turn1search2]
- QwikDev/partytown GitHub (Releases / CHANGELOG). [turn3view0, turn5view0]
- BuilderIO / builder — GitHub releases (SDK / react package releases). [turn0search6]
- @builder.io/react package registry / Yarn (version listing). [turn0search7]
- Builder.io docs — Content API versions (v3 recommended). [turn0search2]
If you want, I can fetch and paste the exact changelog text for Partytown v0.7.6 and the precise latest Builder.io package version (with dates). Which would you prefer?
Consider upgrading Partytown from v0.7.6 to the current version (0.10.3+).
So here's the thing—Partytown v0.7.6 was released quite a while ago. The npm registry shows the latest version is currently 0.10.3, with 0.11.2 available on the GitHub releases page. When you're shipping JavaScript, you want to stay reasonably current with your dependencies. There have been improvements, bug fixes, and likely performance enhancements since 0.7.6 that would benefit your setup.
This is a straightforward upgrade—just update your package to the latest and test it out. You'll get better performance and fewer edge cases to worry about. 🚀
🤖 Prompt for AI Agents
public/~partytown/partytown.js lines 1-2, the repo ships Partytown v0.7.6 which
is outdated; update to the current stable (>=0.10.3, or 0.11.2 from GitHub) by
bumping the Partytown dependency in package.json (or run npx
@builder.io/partytown@latest init), reinstalling, and replacing the runtime
files in public/~partytown with the files from the new release (partytown.js,
sandbox-sw.html/atomics.js, and service worker files), then verify and adjust
any init/config options or swPath/lib base path if the new version changed
defaults and run full app tests to confirm no behavioral regressions.
Delete unused backup files (style.css.bak, new-moon.css.bak) that were superseded by active CSS files. Remove jsconfig.json as it is redundant with tsconfig.json for TypeScript/Astro projects. code written with the help of Claude code
Build output should be generated by CI/CD, not tracked in git. The dist/ folder is already in .gitignore. code written with the help of Claude code
The codebase uses pure Astro components with vanilla JavaScript and standard Markdown. React and MDX integrations were never used. Removed dependencies: - @astrojs/react, @astrojs/mdx - react, react-dom - @types/react, @types/react-dom Also removes .astro/ from version control (generated files). code written with the help of Claude code
The dark mode had a three-state bug because dark.css was not being loaded on initial page load when theme was already set to dark in localStorage. Now properly loads the stylesheet on initialization. This also fixes tags not changing colors in dark mode since they depend on CSS variables from dark.css. code written with the help of Claude code
Tags in blog posts had hardcoded colors (#f0f0f0, #666, #eee) that didn't respond to dark mode. Now using CSS variables for proper theme support. code written with the help of Claude code
Delete public/~partytown/ directory and all contents. No references found in codebase (components, layouts, config) and no package dependencies. Reduces public asset footprint. code written with the help of Claude code
Delete thumbnails and images not referenced in any source files: - astro.png, gatsby.png, community.png, github.png - google-cloud.png, original-tag.png, python.png - logo.png, code-flat.png code written with the help of Claude code
Accessibility (94 → 100): - Fix color contrast by changing --text-muted from #888888 to #595959 (improves contrast ratio from 3.54:1 to 7:1 against white) - Update dark mode --text-muted to #999999 for consistency - Add aria-labels to navigation and footer links SEO (91 → 100): - Add descriptive aria-labels to all external links - Add aria-label to main navigation landmark Also removes dead code: - Delete src/content/pages/about.md (superseded by src/pages/about.astro) code written with the help of Claude code
- Remove underlines from nav links, use subtle opacity hover effect - Remove underline from brand link on hover - Add preconnect hints for Google Fonts to improve loading performance code written with the help of Claude Code
- Update email to aravind.putrevu+website@gmail.com - Remove old action.yml (legacy Gatsby workflow) - Remove CLAUDE.md code written with the help of Claude Code
Prevent Claude Code settings from being committed to public repo code written with the help of Claude Code
- Replace outdated Gatsby instructions with Astro - Add tech stack, project structure, and deployment info - Keep it crisp and developer-friendly code written with the help of Claude Code
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/style.css (1)
168-173: 🐛 Invalid CSS syntax - quotes don't belong in property values!Hey friend, looks like some CSS-in-JS syntax snuck into the stylesheet! CSS doesn't use quotes around property values like JavaScript objects do.
🔎 Proposed fix
.newletter-container-size { width: 760px; height: 150px; - border: "1px solid #EEE"; - background: "white" + border: 1px solid #EEE; + background: white; }
🧹 Nitpick comments (10)
astro.config.mjs (1)
1-18: Heads up: Config might not match PR description 🤔The PR description mentions setting up "RSS feed, MDX, and React support" as integrations, but I only see
sitemap()here. If those features are actually needed, you might be missing:
@astrojs/mdx@astrojs/react- RSS setup (typically in a separate
src/pages/rss.xml.jsthough)If they were intentionally removed during cleanup (which the AI summary suggests), that's totally cool - just making sure this aligns with your intentions!
Also, those empty
remarkPlugins: []andrehypePlugins: []arrays on lines 15-16? Totally optional to keep them around if you're not planning to add plugins soon. They don't hurt anything though!🔎 Optional cleanup
markdown: { shikiConfig: { theme: 'nord', wrap: true - }, - remarkPlugins: [], - rehypePlugins: [], + } },src/pages/about.astro (1)
4-25: Nice data structure for the timeline! 👍The timeline array is clean and readable. One thing you might consider: if this timeline grows or you want to manage it separately, you could move it to a content collection or a separate data file. But for 4 entries, keeping it inline is totally reasonable!
src/components/astro/Footer.astro (1)
2-4: Hey friend! Consider usingtoLocaleDateStringfor cleaner date formatting! 🗓️The months array approach totally works, but we can make this a bit more elegant using the built-in
toLocaleDateStringmethod - less code to maintain, ya know?🔎 Proposed refactor
--- const currentDate = new Date(); -const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; -const lastUpdated = `${months[currentDate.getMonth()]} ${currentDate.getFullYear()}`; +const lastUpdated = currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); ---src/components/astro/Nav.astro (2)
23-36: Consider adding the CSS link to<head>during SSR to prevent FOUC! ⚡Right now the dark CSS gets injected client-side after the page loads. For users with
theme: 'dark'in localStorage, they might see a brief flash of light mode before the dark styles kick in.One pattern I dig is to inline a tiny blocking script in the
<head>(in BaseLayout) that adds the dark class AND the stylesheet link synchronously before the body renders. That way, no flash!
38-44: There's redundant dark mode initialization you can consolidate 🎯You're querying
localStoragefor the theme in bothBaseLayout.astro(line 55) and here inNav.astro(line 39). Both check if the theme is'dark'and add the class—but here you're also handling the CSS loading and button text, which is the complete picture. The BaseLayout one is doing redundant work that could be removed.Move all the dark mode initialization logic to one place—probably here in
Nav.astrosince it's doing the full job anyway. This way you're not querying localStorage twice on page load and you've got a single source of truth for theme initialization.src/pages/[...slug].astro (2)
30-31: The description could be more SEO-friendly! 📈Using the title as the description means search engines see the same text twice. If your content has a
descriptionfield in frontmatter, you might want to use that instead:🔎 Proposed improvement
const title = `${entry.data.title} - Aravind Putrevu`; -const description = entry.data.title; +const description = entry.data.description || entry.data.title;
6-20: The slug collision risk here is theoretical, not actual—but still worth thinking about!Here's the deal, my friend: you're right that merging four collections into a single catch-all route could create slug collisions. Astro will silently use the last matching path, and that's not ideal. But I checked your content directories and you're currently in the clear—all 24 unique slugs across your posts, guides, and presentations are completely unique. Nice work keeping that clean!
That said, the
pagescollection exists in your code but is empty, so there's room for growth here. As you add more content topagesor expand your collections, this could become a real problem.Rather than treating this as a critical issue right now, I'd suggest keeping one of these approaches in mind for later:
- Prefix slugs by type (e.g.,
/posts/my-slug,/guides/my-guide)- Add a build-time check for duplicate slugs and throw an error if found
- Use your content naming conventions to prevent collisions by convention
For now, your content is organized well enough that this won't bite you, but it's good thinking ahead!
src/layouts/BaseLayout.astro (2)
35-44: Consider addingog:urlandog:imagefor richer social previews! 🐦The Open Graph and Twitter Card setup is solid, but social platforms really love having
og:urlandog:imagetoo! When someone shares your blog post on Twitter or LinkedIn, having an image makes it way more engaging.🔎 Proposed additions
<!-- Open Graph / Social Media --> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:type" content="website" /> + <meta property="og:url" content={Astro.url} /> + <meta property="og:image" content="/thumbnails/default-og.png" /> <!-- Twitter Card --> <meta name="twitter:card" content="summary" /> + <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:creator" content="@aravindputrevu" /> <meta name="twitter:title" content={title} /> <meta name="twitter:description" content={description} /> + <meta name="twitter:image" content="/thumbnails/default-og.png" />You'll want to create a default OG image or pass it as a prop for individual pages!
53-59: This dark mode script is duplicated in Nav.astro 🔄As mentioned in the Nav.astro review, this initialization logic exists in both places. This script only adds the class but doesn't load the CSS - the Nav.astro script handles both. Consider removing this one and letting Nav.astro handle all dark mode initialization to keep things DRY!
src/style.css (1)
642-644: Clean up those Gatsby CSS class selectors—they're not doing anything anymore 🧹Since you've migrated to Astro, the
.gatsby-image-wrapperand.gatsby-resp-image-linkselectors are just hanging out in your stylesheet with nothing to match. You've got a few of these scattered around (lines 642, 1099, 1107, 1369, and 1417). Removing them will make your stylesheet leaner and easier to maintain moving forward.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
.github/workflows/action.yml.gitignoreREADME.mdastro.config.mjsjsconfig.jsonpackage.jsonpublic/dark.csssrc/components/astro/Footer.astrosrc/components/astro/Nav.astrosrc/layouts/BaseLayout.astrosrc/pages/[...slug].astrosrc/pages/about.astrosrc/style.csstsconfig.json
💤 Files with no reviewable changes (2)
- jsconfig.json
- .github/workflows/action.yml
✅ Files skipped from review due to trivial changes (1)
- tsconfig.json
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
README.md
31-31: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (11)
.gitignore (2)
60-62: Perfect Astro ignore patterns! ✨The additions look spot on -
dist/and.astro/are exactly what you want to keep out of version control for an Astro project, and.claude/is good practice for tool-specific config.Also applies to: 77-78
57-59: This is the way! 🎯You nailed it! The removal of
public/from .gitignore is absolutely correct for Astro. Out of the box, Astro puts your build output indist/(which you're already ignoring), andpublic/is reserved for static assets that belong in version control—just like the images and CSS files you've got there. No fumbling required! Astro handles this separation beautifully by design, so your build artifacts will never land inpublic/. Clean, intentional, and exactly what you want.src/pages/about.astro (2)
28-61: Great accessibility with those aria-labels! ♿Love that you included
aria-labelattributes on all the social links - that's exactly what we want to see for screen reader users!One heads up though: Line 58 has your email address directly in the
href. While the+websitealias is clever for filtering, this email will be visible in the page source to scrapers and bots. If spam is a concern, you might want to consider:
- A contact form instead
- Obfuscation techniques
- Accepting that modern spam filters handle it (which they usually do pretty well these days)
Totally your call based on your spam tolerance! 😊
64-170: Solid responsive styling! 📱The scoped styles look great with:
- Good use of CSS custom properties for theming
- Responsive breakpoints at 800px and 500px
- Timeline that collapses nicely on mobile
The layout is clean and the spacing is consistent. Nice work!
src/components/astro/Footer.astro (1)
7-17: LGTM! Great accessibility practices here! ♿✨Love seeing those
rel="noopener noreferrer"attributes on external links and the descriptivearia-labelattributes. The semantic<nav>with properaria-label="Footer navigation"is exactly what we want. This is the way!src/components/astro/Nav.astro (1)
46-63: Nice work on the toggle implementation! Clean and functional! 🌓The optional chaining on the event listener, the class toggling, localStorage management, and dynamic CSS loading/removal - it's all working together nicely. The use of
?.remove()for cleanup is 👨🍳💋.src/pages/[...slug].astro (1)
38-47: Solid conditional rendering pattern! 👍The
'date' in entry.data && entry.data.datepattern is a clean way to handle optional fields across different collection types. Same for tags. TypeScript will be happy, and so am I!public/dark.css (2)
1-28: Nice work on the CSS custom properties setup! 🎨Love the organization here - core colors, legacy mappings for compatibility, and clear sectioning. The brutalist theme variables are well thought out. Having both the new semantic names AND legacy mappings means existing code won't break. Smart!
121-130: The duplicate border issue has been fixed! ✅I see the previous review flagged a duplicate
borderproperty in the[type='search']selector. Looking good now - single border declaration in the base rule, and the focus state has its own properly scoped border. Nice cleanup!src/layouts/BaseLayout.astro (1)
18-51: Overall layout structure looks great! 🏗️The semantic HTML structure with proper
<main>element, the slot pattern for content injection, and the component composition (Nav → main → Footer) is exactly what I'd expect from a well-structured Astro layout. The preconnect hints for Google Fonts are a nice performance touch too!src/style.css (1)
1-33: Solid CSS custom properties foundation! 🎨The variable naming convention with core colors, legacy mappings, and semantic UI element tokens is well organized. Having the code font family variable is a nice touch too. This pairs nicely with the dark.css overrides!
| { | ||
| "name": "aravind.dev", | ||
| "private": true, | ||
| "version": "1.0.0", | ||
| "description": "Personal website running on Gatsby, React, and Node.js.", | ||
| "version": "2.0.0", | ||
| "type": "module", | ||
| "description": "Personal website running on Astro and Node.js.", | ||
| "original-author": "Tania Rascia", | ||
| "author": "Aravind Putrevu", | ||
| "license": "MIT", | ||
| "scripts": { | ||
| "build": "gatsby build", | ||
| "develop": "gatsby develop", | ||
| "start": "npm run develop", | ||
| "serve": "gatsby serve", | ||
| "clean": "gatsby clean", | ||
| "preinstall": "npx npm-force-resolutions" | ||
| "dev": "astro dev", | ||
| "start": "astro dev", | ||
| "build": "astro check && astro build", | ||
| "preview": "astro preview", | ||
| "astro": "astro" | ||
| }, | ||
| "dependencies": { | ||
| "axios": "^1.7.7", | ||
| "gatsby": "^5.11.0", | ||
| "gatsby-cli": "^5.11.0", | ||
| "gatsby-image": "^3.4.0", | ||
| "gatsby-plugin-feed": "^5.13.1", | ||
| "gatsby-plugin-local-search": "^2.0.1", | ||
| "gatsby-plugin-page-creator": "^5.13.1", | ||
| "gatsby-plugin-plausible-analytics": "^0.0.8", | ||
| "gatsby-plugin-react-helmet": "^6.13.1", | ||
| "gatsby-plugin-robots-txt": "^1.8.0", | ||
| "gatsby-plugin-sharp": "^5.13.1", | ||
| "gatsby-plugin-sitemap": "^6.13.1", | ||
| "gatsby-plugin-twitter": "^5.13.1", | ||
| "gatsby-plugin-typescript": "^5.13.1", | ||
| "gatsby-remark-autolink-headers": "^6.13.1", | ||
| "gatsby-remark-embedder": "^7.0.0", | ||
| "gatsby-remark-images": "^7.13.2", | ||
| "gatsby-remark-prismjs": "^7.13.2", | ||
| "gatsby-remark-table-of-contents": "^1.0.0", | ||
| "gatsby-source-filesystem": "^5.13.1", | ||
| "gatsby-transformer-remark": "^6.13.1", | ||
| "gatsby-transformer-sharp": "^5.13.1", | ||
| "http-server": "^0.12.3", | ||
| "postcss": "^8.1.7", | ||
| "postcss-flexbugs-fixes": "^5.0.2", | ||
| "postcss-loader": "^5.0.0", | ||
| "prismjs": "^1.23.0", | ||
| "query-string": "^7.0.0", | ||
| "react": "^18.2.0", | ||
| "react-dom": "^18.2.0", | ||
| "react-helmet": "^6.1.0", | ||
| "react-use-flexsearch": "^0.1.1", | ||
| "webpack-dev-middleware": "^7.4.2" | ||
| "@astrojs/check": "^0.9.4", | ||
| "@astrojs/rss": "^4.0.9", | ||
| "@astrojs/sitemap": "^3.2.1", | ||
| "astro": "^4.16.14", | ||
| "typescript": "^5.6.3" | ||
| }, | ||
| "devDependencies": { | ||
| "eslint": "^7.26.0", | ||
| "eslint-config-react-app": "^6.0.0", | ||
| "gatsby-plugin-manifest": "^5.13.1", | ||
| "npm-force-resolutions": "^0.0.10" | ||
| "prettier": "^3.3.3", | ||
| "prettier-plugin-astro": "^0.14.1" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/aravindputrevu/aravind.dev" | ||
| }, | ||
| "resolutions": { | ||
| "gatsby-react-router-scroll": "6.11.0", | ||
| "graphql": "15.8.0" | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
latest stable version Astro npm package 2024 2025
💡 Result:
As of January 3, 2026 the latest stable npm release of the astro package is version 5.13.5. [1]
Sources:
[1] npm package page — astro.
🌐 Web query:
latest stable version @astrojs/check @astrojs/rss @astrojs/sitemap TypeScript npm
💡 Result:
As of January 3, 2026 — latest stable npm versions:
- @astrojs/check — 0.9.4. [1]
- @astrojs/rss — 4.0.6. [2]
- @astrojs/sitemap — 3.5.1. [3]
- typescript — 5.8.3. [4]
Sources: [1] npm package @astrojs/check; [2] npm package @astrojs/rss; [3] npm package @astrojs/sitemap; [4] npm package typescript.
🏁 Script executed:
# Check if ESLint configuration or references exist in the codebase
fd -e 'eslintrc*' -e 'eslintignore' 2>/dev/null || echo "No eslintrc files found"
rg "eslint" -i --type json --type yaml --type toml 2>/dev/null | head -20Repository: aravindputrevu/aravind.dev
Length of output: 52
🏁 Script executed:
# Check git history to see if ESLint was recently removed
git log --oneline --all -- '.eslintrc*' 2>/dev/null | head -10Repository: aravindputrevu/aravind.dev
Length of output: 92
Astro migration is looking good! 🧙♂️
Love the setup here:
"type": "module"✓ correct for Astroastro checkin the build script is the real deal — catching TypeScript errors at build time is powerful stuff- Caret ranges (^) give you flexibility for patch and minor updates
Quick heads up on versions though: Astro's actually at 5.13.5 now (you're on ^4.16.14), and a few other packages could use updates too:
- @astrojs/sitemap has moved to 3.5.1 (you're on ^3.2.1)
- TypeScript is at 5.8.3 (you're on ^5.6.3)
- @astrojs/check and @astrojs/rss look solid
Also, about ESLint — it looks like it was never in this repo to begin with (no .eslintrc found), so you're all good there! TypeScript + astro check is a solid approach for catching issues without a separate linter.
🤖 Prompt for AI Agents
In package.json around lines 1 to 32, the review notes some packages are behind
their current stable versions; update the dependency versions to compatible
latest releases (e.g., set "astro" to "^5.13.5", "@astrojs/sitemap" to "^3.5.1",
and "typescript" to "^5.8.3"), keep caret ranges for flexibility, run npm
install (or pnpm/yarn) to update the lockfile, then run astro check and a full
build to verify compatibility and fix any resulting TypeScript or integration
issues.
| # aravind.dev | ||
|
|
||
| Source code for [aravind.dev](https://aravind.dev) blog. | ||
| This is the [Original Theme Source](https://github.com/taniarascia/taniarascia.com). | ||
| Personal website and blog built with [Astro](https://astro.build). | ||
|
|
||
| # Prerequisites | ||
| ## Tech Stack | ||
|
|
||
| - Node | ||
| - Gatsby | ||
| - **Framework:** Astro 4 | ||
| - **Styling:** Vanilla CSS (Brutalist theme) | ||
| - **Content:** Markdown + Astro components | ||
| - **Deployment:** Firebase Hosting | ||
| - **CI/CD:** GitHub Actions | ||
|
|
||
| # Building the project | ||
| ## Quick Start | ||
|
|
||
| Below command installs all the necessary modules from the | ||
| ```bash | ||
| # Install dependencies | ||
| npm install | ||
|
|
||
| # Start dev server (http://localhost:4321) | ||
| npm run dev | ||
|
|
||
| # Build for production | ||
| npm run build | ||
|
|
||
| # Preview production build | ||
| npm run preview | ||
| ``` | ||
| npm install | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| src/ | ||
| ├── components/astro/ # Reusable components (Nav, Footer) | ||
| ├── content/ | ||
| │ ├── posts/ # Blog posts (markdown) | ||
| │ └── guides/ # Technical guides (markdown) | ||
| ├── layouts/ # Page layouts | ||
| ├── pages/ # Routes (index, blog, about, archive) | ||
| └── style.css # Global styles | ||
|
|
||
| public/ | ||
| ├── images/ # Static images | ||
| └── thumbnails/ # Post thumbnails | ||
| ``` | ||
| gatsby develop | ||
|
|
||
| ## Content | ||
|
|
||
| Blog posts and guides are written in Markdown with frontmatter: | ||
|
|
||
| ```markdown | ||
| --- | ||
| title: 'Post Title' | ||
| date: '2024-01-01' | ||
| tags: ['tag1', 'tag2'] | ||
| thumbnail: '../thumbnails/image.png' | ||
| --- | ||
|
|
||
| Content here... | ||
| ``` | ||
|
|
||
| Please read more about each [plugin](https://www.gatsbyjs.com/plugins/) and about [gatsby](https://www.gatsbyjs.com/docs/quick-start/) further. No newline at end of file | ||
| ## Deployment | ||
|
|
||
| Push to `main` triggers automatic deployment to Firebase Hosting via GitHub Actions. | ||
|
|
||
| **Branch previews:** Push to any branch to get a preview deployment. | ||
|
|
||
| ## Local Development | ||
|
|
||
| The site uses a brutalist design with light/dark mode support. Dark mode styles are in `public/dark.css` and loaded dynamically. | ||
|
|
||
| ## License | ||
|
|
||
| Content is copyrighted. Code is available for reference. |
There was a problem hiding this comment.
Fantastic documentation update! 📚
This README is super helpful and accurately reflects the Astro migration. Love the clear structure with Tech Stack, Quick Start, and Project Structure sections. Really makes it easy for someone to jump in!
One tiny thing: Line 31's code fence is missing a language identifier (markdownlint is complaining about it). No big deal, but it helps with syntax highlighting!
🔎 Quick fix
-```
+```plaintext
src/
├── components/astro/ # Reusable components (Nav, Footer)🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
31-31: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In README.md around lines 1 to 73 (specifically the code block showing the
Project Structure starting at line ~31), the opening triple-backtick fence has
no language identifier which triggers markdownlint; update the opening fence to
include a language (e.g., replace ``` with ```plaintext) so the code block is
labeled and the linter warning is resolved.
- Fix broken Markdown link in elastic-app-search-python.md - Add postinstall script for automated Astro type generation - Reorganize dependencies (move TypeScript types to devDependencies) - Add engines field to specify Node.js version requirement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/pages/index.astro (2)
9-11: Epic sorting! Let's add some defensive programming though! 🛡️If any post is missing a
dateor has an invalid date, this will throw during build. While that's actually good for catching data issues early, adding optional chaining makes the code more resilient and gives better error context:🔎 More resilient sorting approach
const sortedPosts = allPosts.sort((a, b) => { - return b.data.date.getTime() - a.data.date.getTime(); + return (b.data.date?.getTime() ?? 0) - (a.data.date?.getTime() ?? 0); });This way, posts with missing dates fall to the end rather than crashing the build. Even better, you could add a build-time validation that throws a descriptive error if dates are missing!
30-37: Love the semantic HTML here! 🎯The list structure is super accessible and the link generation looks clean. One small enhancement: consider adding an empty state when there are no posts (probably rare, but it's a nice touch for robustness):
🔎 Optional empty state enhancement
<ul class="posts"> + {sortedPosts.length === 0 && ( + <li class="empty-state">No posts yet. Check back soon!</li> + )} {sortedPosts.map((post) => ( <li> <a href={`/${post.slug}`}>{post.data.title}</a> <span class="year">{post.data.date.getFullYear()}</span> </li> ))} </ul>
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/pages/index.astro
🔇 Additional comments (2)
src/pages/index.astro (2)
42-115: This CSS is absolutely fantastic! 🎨Really loving the approach here:
- ✅ Using CSS variables for theming (dark mode ready!)
- ✅ Scoped styles keep things clean and maintainable
- ✅ Responsive design with sensible breakpoint
- ✅ Great hover states for accessibility
- ✅ Semantic spacing and typography
The max-width of 600px is perfect for readability (right in that sweet spot for line length), and the flexbox layout for the post list is chef's kiss! 👨🍳
5-7: Good news—this filter is totally solid! 🎉The schema defines
hidden: z.boolean().default(false), so thehiddenfield will always be a boolean value. That means!data.hiddenwill always work exactly as intended—showing all posts except those explicitly marked withhidden: true. No undefined surprises here! The code is crystal clear and doing the right thing.
Adds missing React, MDX, and type dependencies to lockfile to fix npm ci in CI workflow. code written with the help of Claude Code
code written with the help of Claude Code
- Add RSS feed at /rss.xml with latest 30 posts - Add Twitter widget script to BaseLayout - Convert bare Twitter URLs to blockquote embeds in 3 posts - Convert bare YouTube URL to iframe embed - Remove duplicate Twitter scripts from open-source-sustainability.md code written with the help of Claude Code
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
src/content/posts/open-source-sustainability.md (1)
70-70: This "Github" → "GitHub" capitalization issue is still outstanding from the previous review.Hey, so this was flagged before—the official branding is "GitHub" with a capital H. You know how important those little details are for accuracy and professionalism. Quick fix:
-2. Some organizations choose to remain independent and raise funds via platforms like Open Collective or Github Sponsors. Do support them via your organization. +2. Some organizations choose to remain independent and raise funds via platforms like Open Collective or GitHub Sponsors. Do support them via your organization.src/content/posts/kid-coding-mandatory.md (1)
39-43: Hold up! This duplicate paragraph is back! 🔄Wait a second... the past review shows this duplicate paragraph issue was addressed in commits 054eba6 to a39d70a, but it's still here in the code! Lines 39-41 and line 41 contain the identical sentence.
Either this needs to be cleaned up again, or there might be a merge conflict that reintroduced it. Let's make sure we remove one of these duplicates!
🔎 Remove the duplicate paragraph
While I appreciate the greater interest or outcome, I don't agree how an Indian parent is looking at this as another avenue for coaching. Also, some marketing campaigns from these EdTech co's are cringeworthy. -While I appreciate the greater interest or outcome, I don't agree how an Indian parent is looking at this as another avenue for coaching. Also, some marketing campaigns from these EdTech co's are cringeworthy. -
🧹 Nitpick comments (6)
src/content/posts/vaccine-for-all.md (1)
52-54: Twitter embed looks good, but let's ensure accessibility.I dig what you've done here—replacing the bare URL with a proper Twitter blockquote embed is chef's kiss for maintainability and user experience. The pattern you're using relies on the Twitter widget script to transform the
<blockquote class="twitter-tweet">into an embedded tweet, which is the standard approach.That said, the
<a>tag inside the blockquote is empty. If the Twitter widget script somehow doesn't load (network issue, third-party script failure), users won't see anything clickable or readable. Consider adding descriptive text to the anchor tag as a fallback:- <blockquote class="twitter-tweet"><a href="https://twitter.com/BDUTT/status/1380005312782954502"></a></blockquote> + <blockquote class="twitter-tweet"><a href="https://twitter.com/BDUTT/status/1380005312782954502">Tweet by Barkha Dutt</a></blockquote>This way, folks get a clickable link if the embed fails to load, and screen readers get actual context. Since the PR notes mention the Twitter widget script was added to BaseLayout, this should work seamlessly—but defensive coding is always a win.
src/pages/rss.xml.js (2)
15-15: Nice touch limiting to 30 posts! (Optional: extract that magic number) ✨The hardcoded
30works fine, but if you ever want to change it, you might want to extract it as a constant at the top of the file:+const RSS_POST_LIMIT = 30; + export async function GET(context) { const posts = await getCollection('posts', ({ data }) => !data.hidden); const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime() ); return rss({ title: 'Aravind Putrevu', description: 'Helping developers solve their problems!', site: context.site, - items: sortedPosts.slice(0, 30).map((post) => ({ + items: sortedPosts.slice(0, RSS_POST_LIMIT).map((post) => ({This makes it easier to find and update later. But hey, if 30 is your magic number forever, you're good! 😄
11-22: Your RSS feed setup is solid! Here's what I found:✅ Site configuration: You're all good here—
siteis properly set to'https://www.aravind.dev'in yourastro.config.mjs, socontext.sitewill work perfectly.✅ Link format: Your
/${post.slug}/matches your[...slug].astrodynamic route beautifully. Links will resolve correctly.🎯 Optional enhancement: Since your post schema already includes optional
tagsandcategoriesfields, consider adding them to your RSS items. This gives readers and aggregators more context about your posts:categories: post.data.tags || []This is a nice-to-have that makes your feed more useful, especially if you're using tags to categorize content.
src/content/posts/kid-coding-mandatory.md (2)
15-17: Twitter embeds look great! Consider adding fallback text though! 🐦Nice work converting the bare URLs to proper Twitter blockquote embeds! The structure looks correct and will work with the Twitter widgets script you're loading in BaseLayout.
One tiny suggestion: add fallback text inside the anchor tags so if the Twitter script is blocked or fails to load, readers still see something:
<div align="center"> -<blockquote class="twitter-tweet"><a href="https://twitter.com/aravindputrevu/status/1308747154932948998"></a></blockquote> +<blockquote class="twitter-tweet"><a href="https://twitter.com/aravindputrevu/status/1308747154932948998">View tweet</a></blockquote> </div>This is a nice progressive enhancement! 😊
57-59: Same great Twitter embed pattern! Same optional enhancement! 🐦Just like the first Twitter embed, this one looks good! And the same suggestion applies - consider adding fallback text for better UX when scripts are blocked:
<div align="center"> -<blockquote class="twitter-tweet"><a href="https://twitter.com/xprilion/status/1308758974846525445"></a></blockquote> +<blockquote class="twitter-tweet"><a href="https://twitter.com/xprilion/status/1308758974846525445">View tweet</a></blockquote> </div>src/layouts/BaseLayout.astro (1)
64-65: Twitter script loads on every page - consider conditional loading! 🐦The Twitter widgets script will load on every single page, even pages without any tweets. This adds unnecessary weight and has privacy implications (Twitter can track users on all pages).
Consider making this conditional - only load it when the page actually has Twitter embeds. You could pass a
hasTwitterEmbedsprop or use a more sophisticated solution.🔎 Optional: Conditional Twitter script loading
Option 1: Simple prop-based approach:
export interface Props { title?: string; description?: string; + hasTwitterEmbeds?: boolean; } const { title = 'Aravind Putrevu', - description = 'Helping developers solve their problems!' + description = 'Helping developers solve their problems!', + hasTwitterEmbeds = false } = Astro.props;Then conditionally include the script:
- <!-- Twitter embeds --> - <script async src="https://platform.twitter.com/widgets.js"></script> + {hasTwitterEmbeds && ( + <!-- Twitter embeds --> + <script async src="https://platform.twitter.com/widgets.js"></script> + )}Option 2: Use a client-side script to load Twitter widgets only if tweet embeds are detected on the page (more advanced but automatic).
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
src/content/posts/kid-coding-mandatory.mdsrc/content/posts/open-source-sustainability.mdsrc/content/posts/vaccine-for-all.mdsrc/layouts/BaseLayout.astrosrc/pages/rss.xml.js
🔇 Additional comments (10)
src/content/posts/open-source-sustainability.md (2)
6-7: Looks solid! The absolute paths are now compatible with Astro's public asset serving.Yeah, so here's the thing—migrating to absolute root paths (
/thumbnails/...and/images/post-images/...) is exactly what you need when moving from Gatsby to Astro. The static file serving works beautifully with this approach, and it's way more predictable than dealing with relative paths across different content structures. Nice work getting these updated!
48-48: No issues with the apostrophe normalization.Just a tiny style thing—the straight apostrophe in the Twitter embed works great. This kind of cleanup happens naturally when content gets processed through different systems, so it's all good!
src/content/posts/vaccine-for-all.md (2)
6-7: Verify image paths resolve correctly in the Astro build.So you've switched to absolute root-relative paths for your images—which is awesome for the Astro migration! But here's the thing: you'll want to make sure those image files actually exist at
/thumbnails/heartbeat.pngand/images/post-images/covid-vaccine.jpegin yourpublic/directory (or wherever Astro expects static assets). If they're off by even a character, you'll end up with broken images in production.I'd recommend running the full build locally (
npm run build) and checking thedist/output to confirm those image references are resolving correctly.
81-81: Image path migration looks solid.The switch from relative to absolute path here aligns perfectly with your Astro structure. Just make sure that
/images/post-images/covid-vaccine-blog-image.jpegactually exists in your public assets. Once you've verified the images are in place (which you can do withnpm run buildand checkingdist/), you're golden.src/pages/rss.xml.js (2)
1-2: Hey! Nice and clean imports! 🎉Looking good here! You're using the official
@astrojs/rsspackage and Astro's content collections API. That's the way to do it!
4-9: No defensive checks needed—your schema's got this! ✨The
datefield in your posts collection is properly defined asz.date()(required, not optional) insrc/content/config.ts. Astro's content layer validates every post against this schema before yourGETfunction even runs, sodata.dateis guaranteed to be a valid Date object. The sorting logic is solid—no runtime errors possible here.src/content/posts/kid-coding-mandatory.md (1)
6-6: Perfect path migration! 🎯Great job updating the thumbnail to an absolute path! That's exactly the right approach for Astro where
/thumbnails/binary.pngmaps topublic/thumbnails/binary.png. This makes asset references much clearer and prevents broken links when content is rendered from different route depths.src/layouts/BaseLayout.astro (3)
1-15: Excellent TypeScript setup and sensible defaults! 🎉Love seeing proper TypeScript interfaces for your props! The optional
titleanddescriptionwith sensible defaults is exactly what you want in a reusable layout component. This makes it super flexible!
46-47: Excellent choice with Plausible Analytics! 🎯Nice! Using Plausible with
deferis perfect - privacy-friendly and non-blocking. Much better than the heavy alternatives!
49-54: Beautiful semantic HTML structure! 🏗️Love the clean separation here - Nav, main content slot, and Footer. Semantic HTML at its finest! The
<main>element is great for accessibility too.
- Add image prop for og:image and twitter:image - Add og:url meta tag using Astro.url - Change twitter:card to summary_large_image - Add crossorigin to fonts.googleapis.com preconnect code written with the help of Claude Code
- Add is:inline directive to prevent Astro modification - Expose window.toggleTheme() for centralized theme control - Handle dark.css loading/unloading in toggle function - Simplify Nav script to use global toggle API code written with the help of Claude Code
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Style
Chores
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.