-
Notifications
You must be signed in to change notification settings - Fork 0
Integrate SEO utilities with Vue #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Warning Rate limit exceeded@gocanto has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 29 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (7)
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughRefactors page-level SEO initialization from a singleton (seo.apply/applyFromPost) to composable hooks (useSeo, useSeoFromPost). Updates multiple Vue pages to call the new hooks. Enhances src/support/seo.ts with SSR guards, DOM-safe helpers, reactive option resolution, and JSON-LD generation for posts. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant V as Vue Component
participant H as useSeo()
participant S as Seo (internal)
participant D as DOM (document/window)
U->>V: Navigates to page
activate V
V->>H: useSeo({ title, description, image })
activate H
H->>H: Resolve reactive options (refs/getters)
H->>S: apply(options)
alt Browser environment
S->>D: Set <title>, <meta>, <link rel=canonical>, JSON-LD
else SSR/No DOM
S-->>H: No-op (guarded)
end
deactivate H
note over V,H: Hook re-applies on options change and cleans up on unmount
deactivate V
sequenceDiagram
autonumber
participant V as PostPage.vue
participant HP as useSeoFromPost(post)
participant H as useSeo()
participant S as Seo
participant D as DOM
V->>HP: useSeoFromPost(post)
activate HP
HP->>HP: Derive SeoOptions + Article JSON-LD from post
HP->>H: useSeo(derivedOptions)
H->>S: apply(derivedOptions)
S->>D: Update title/meta/canonical + JSON-LD (guarded for SSR)
deactivate HP
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @gocanto, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly refactors the application's SEO management by integrating new Vue Composition API helpers. The primary goal is to enable reactive updates of SEO metadata and ensure robust behavior in server-side rendering (SSR) contexts by guarding direct DOM manipulations. This change streamlines how SEO information is applied across various pages, making it more dynamic and resilient.
Highlights
- New Vue Composition Helpers: Introduced
useSeoanduseSeoFromPostfor managing SEO metadata in Vue components, leveraging the Composition API. - Reactive SEO Metadata Application: SEO metadata is now applied reactively, ensuring it stays in sync with Vue component state changes.
- Server-Side Rendering (SSR) Safety: Added checks to guard direct DOM access, preventing errors when components are rendered outside a browser environment, improving SSR compatibility.
- Refactored Page Components: All static pages (
AboutPage,HomePage,ProjectsPage,ResumePage,SubscribePage) have been updated to utilize the newuseSeohelper for consistent SEO application. - Dynamic Post SEO: The
PostPagenow usesuseSeoFromPostto dynamically apply SEO metadata based on fetched post data, removing manualseo.applyFromPostcalls.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in pull request comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces Vue composition helpers for managing SEO metadata reactively, which is a great improvement. The implementation is solid, with good practices like SSR guards and automatic cleanup of watchers. My feedback focuses on improving code style consistency, specifically regarding indentation, and a small refactoring suggestion to enhance readability and maintainability in the new SEO utility file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (9)
src/support/seo.ts (4)
61-63: Use site name for application-name titles (avoid per-route labels).
application-nameandapple-mobile-web-app-titleshould stay constant (the app/site name), not the per-page title.Apply this diff:
- this.setMetaByName('application-name', title); - this.setMetaByName('apple-mobile-web-app-title', title); + this.setMetaByName('application-name', SITE_NAME); + this.setMetaByName('apple-mobile-web-app-title', SITE_NAME);
109-147: Prevent stale meta/link tags when content becomes undefined.Current helpers no-op on falsy content, which can leave outdated tags after reactive updates (e.g., image removed). Prefer removing the tag when content is empty.
Apply this diff:
- private setMetaByName(name: string, content?: string): void { - if (!hasDocument) return; - if (!content) return; - - let element = document.head.querySelector<HTMLMetaElement>(`meta[name="${name}"]`); - if (!element) { - element = document.createElement('meta'); - element.setAttribute('name', name); - document.head.appendChild(element); - } - element.setAttribute('content', content); - } + private setMetaByName(name: string, content?: string): void { + if (!hasDocument) return; + const selector = `meta[name="${name}"]`; + let element = document.head.querySelector<HTMLMetaElement>(selector); + if (!content) { + if (element) element.remove(); + return; + } + if (!element) { + element = document.createElement('meta'); + element.setAttribute('name', name); + document.head.appendChild(element); + } + element.setAttribute('content', content); + } - private setMetaByProperty(property: string, content?: string): void { - if (!hasDocument) return; - if (!content) return; - let element = document.head.querySelector<HTMLMetaElement>(`meta[property="${property}"]`); - if (!element) { - element = document.createElement('meta'); - element.setAttribute('property', property); - document.head.appendChild(element); - } - element.setAttribute('content', content); - } + private setMetaByProperty(property: string, content?: string): void { + if (!hasDocument) return; + const selector = `meta[property="${property}"]`; + let element = document.head.querySelector<HTMLMetaElement>(selector); + if (!content) { + if (element) element.remove(); + return; + } + if (!element) { + element = document.createElement('meta'); + element.setAttribute('property', property); + document.head.appendChild(element); + } + element.setAttribute('content', content); + } - private setLink(rel: string, href?: string): void { - if (!hasDocument) return; - if (!href) return; - let element = document.head.querySelector<HTMLLinkElement>(`link[rel="${rel}"]`); - if (!element) { - element = document.createElement('link'); - element.setAttribute('rel', rel); - document.head.appendChild(element); - } - element.setAttribute('href', href); - } + private setLink(rel: string, href?: string): void { + if (!hasDocument) return; + const selector = `link[rel="${rel}"]`; + let element = document.head.querySelector<HTMLLinkElement>(selector); + if (!href) { + if (element) element.remove(); + return; + } + if (!element) { + element = document.createElement('link'); + element.setAttribute('rel', rel); + document.head.appendChild(element); + } + element.setAttribute('href', href); + }
149-165: Consider multiple JSON‑LD blocks support.Using a single fixed id overwrites other structured data. If you anticipate multiple entities, accept an array or allow keyed scripts (e.g., data-seo-key).
202-209: Run SEO writes after render.Use
flush: 'post'to avoid DOM writes before paint/hydration.Apply this diff:
- const stop = watchEffect(() => { + const stop = watchEffect(() => { const resolved = resolveValue(options); if (!resolved) return; seo.apply(resolved); - }); + }, { flush: 'post' });src/pages/ProjectsPage.vue (1)
76-80: Copy tweak for grammar/clarity.Possessive missing on site name; minor polish.
Apply this diff:
useSeo({ title: 'Projects', - description: `Explore some of ${SITE_NAME} open source and client projects built to solve real engineering challenges.`, + description: `Explore some of ${SITE_NAME}'s open‑source and client projects built to solve real engineering challenges.`, image: ogImage, });src/pages/SubscribePage.vue (1)
169-173: Copy tweak for grammar.Reads more naturally and avoids gendered pronouns.
Apply this diff:
useSeo({ title: 'Subscribe', - description: `Subscribe to ${SITE_NAME}'s newsletter to updates of articles and cool things he is working on.`, + description: `Subscribe to ${SITE_NAME}'s newsletter for updates on new articles and projects.`, image: ogImage, });src/pages/ResumePage.vue (1)
68-72: Optional copy tweak.Slightly more concise.
Apply this diff:
useSeo({ title: 'Resume', - description: `Explore the experience, education, and recommendations of ${SITE_NAME}.`, + description: `Explore ${SITE_NAME}'s experience, education, and recommendations.`, image: ogImage, });src/pages/AboutPage.vue (1)
110-114: Make SEO reactive and include brand in the title.
Leverage MaybeRef/Getters so metadata updates when profile loads; also add site name to title.-useSeo({ - title: 'About', - description: `${SITE_NAME} is an engineering leader who’s passionate about building reliable and smooth software.`, - image: AboutPicture, -}); +useSeo({ + title: () => `About — ${SITE_NAME}`, + description: () => `${formattedNickname.value} is an engineering leader passionate about building reliable, smooth software.`, + image: () => aboutPicture.value, +});src/pages/PostPage.vue (1)
139-140: Confirm undefined-safety and consider typing post as nullable.
Assuming the composable watches the ref and guards undefined. If not, add a null check; also prefer a nullable ref type for clarity.Outside the changed lines, consider:
-const post = ref<PostResponse>(); +const post = ref<PostResponse | null>(null);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/pages/AboutPage.vue(2 hunks)src/pages/HomePage.vue(1 hunks)src/pages/PostPage.vue(3 hunks)src/pages/ProjectsPage.vue(2 hunks)src/pages/ResumePage.vue(2 hunks)src/pages/SubscribePage.vue(1 hunks)src/support/seo.ts(5 hunks)
🔇 Additional comments (11)
src/support/seo.ts (2)
40-46: Good SSR guard.Early-returning when DOM isn’t available prevents crashes during SSR/SSG. Looks solid.
215-243: LGTM on post-derived SEO.Computed options + hook composition reads well. This will update on post changes and cleanly handles SSR no-ops.
Double-check that
cover_image_urlcan be null; with the helper fixes above,og:imagewill be removed when absent rather than lingering.src/pages/ProjectsPage.vue (1)
61-61: Migration to useSeo is correct.src/pages/SubscribePage.vue (1)
162-162: Migration to useSeo is correct.src/pages/HomePage.vue (2)
56-56: Migration to useSeo is correct.
62-66: LGTM.Home metadata looks fine and will resolve absolute image via helper.
src/pages/ResumePage.vue (1)
57-57: Migration to useSeo is correct.src/pages/AboutPage.vue (1)
84-84: LGTM: migrated to composable SEO API.
Import looks correct and aligns with the new API.src/pages/PostPage.vue (3)
117-117: LGTM: switch to useSeoFromPost.
Import matches the new public API.
142-144: LGTM: marked config.
GFM + breaks are reasonable defaults.
200-201: Ignore — getPost returns PostResponse (no .data wrapper).getPost is declared
async getPost(slug: string): Promise<PostResponse>(src/stores/api/store.ts:123) and the unit test (tests/stores/api/store.test.ts:83–86) asserts it returns the raw post object;getProfileis the method that returns an{ data: ... }wrapper. The suggested change is unnecessary.Likely an incorrect or invalid review comment.
Summary
Summary by CodeRabbit