Skip to content

Refactor footer component structure and enhance copyright notice with… - #41

Merged
ebulgakov merged 6 commits into
mainfrom
feature/update-footer-header
Aug 3, 2026
Merged

Refactor footer component structure and enhance copyright notice with…#41
ebulgakov merged 6 commits into
mainfrom
feature/update-footer-header

Conversation

@ebulgakov

@ebulgakov ebulgakov commented Aug 3, 2026

Copy link
Copy Markdown
Owner

… GitHub link

Summary by CodeRabbit

  • New Features
    • Added a refreshed app header with branding, authentication controls, and navigation.
    • Added navigation for published collections and authenticated users’ collections.
    • Added reusable logo, wordmark, and combined branding components.
  • Improvements
    • Updated the footer with a cleaner layout and border styling.
    • Copyright information now links securely to GitHub and opens in a new tab.
    • Simplified branding by displaying the logo without redundant application-name text.
  • Documentation
    • Added visual component examples for logo and wordmark variants.

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
linkfolio Ready Ready Preview Aug 3, 2026 5:24pm

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ebulgakov, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a52bea3f-77d3-4c71-b422-b26f8c7f7764

📥 Commits

Reviewing files that changed from the base of the PR and between b19a7b3 and e88cbf8.

⛔ Files ignored due to path filters (1)
  • public/linkfolio-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • app/shared/ui/logo/app-logo-wordmark.vue
  • app/shared/ui/logo/app-logo.vue
  • app/shared/ui/logo/app-wordmark.vue
  • nuxt.config.ts
📝 Walkthrough

Walkthrough

The app shell now uses direct header and footer components, an authenticated navigation menu, separate logo components, and an external GitHub copyright link. The footer links component was removed, and the personal collections link moved into the navigation menu.

Changes

App shell navigation

Layer / File(s) Summary
Authenticated navigation shell
app/shared/api/use-session.ts, app/features/nav-menu/*, app/ui/app-header.vue, app/features/auth-buttons/*, app/app.vue
The shared session hook preserves SSR cookie forwarding. The header renders the wordmark, authentication controls, and navigation menu. Personal collections appear only for authenticated users.
Logo component structure
app/shared/ui/logo/*, app/shared/ui/index.ts
The shared UI exports separate logo, wordmark, and combined logo-wordmark components. Storybook stories cover each component.
Footer and copyright content
app/ui/app-footer.vue, app/ui/app-footer/footer-links.vue, app/features/copyright/ui/copyright-notice.vue, i18n/locales/*.json
The footer removes the footer-links component and adds a border. The copyright notice becomes a secure external GitHub link with localized accessibility labels.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppHeader
  participant NavMenu
  participant useNavMenu
  participant useAuthSession
  participant AuthClient
  AppHeader->>NavMenu: Render navigation menu
  NavMenu->>useNavMenu: Load session
  useNavMenu->>useAuthSession: Request session
  useAuthSession->>AuthClient: Fetch session with SSR cookie header
  AuthClient-->>useAuthSession: Return session data
  useAuthSession-->>useNavMenu: Return session
  useNavMenu-->>NavMenu: Provide session
  NavMenu-->>AppHeader: Render published and conditional personal links
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the footer refactor and copyright notice enhancement, which are central changes in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/update-footer-header

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

❤️ Share

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

@ebulgakov
ebulgakov marked this pull request as ready for review August 3, 2026 16:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
app/shared/ui/logo/app-logo-wordmark.stories.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the shared UI barrel in both stories.

  • app/shared/ui/logo/app-logo-wordmark.stories.ts#L1-L1: import AppLogoWordmark from ~/shared/ui.
  • app/shared/ui/logo/app-wordmark.stories.ts#L1-L1: import AppWordmark from ~/shared/ui.

As per coding guidelines, app/shared/{api,ui,lib}/**/*.{ts,vue} must import shared modules through their segment barrels rather than deep paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/shared/ui/logo/app-logo-wordmark.stories.ts` at line 1, Update the
AppLogoWordmark import in app/shared/ui/logo/app-logo-wordmark.stories.ts at
lines 1-1 to use the shared UI barrel. Also update the AppWordmark import in
app/shared/ui/logo/app-wordmark.stories.ts at lines 1-1 to import from the same
barrel.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/features/copyright/ui/copyright-notice.vue`:
- Around line 5-19: Update the GitHub anchor in the template to provide an
accessible name that identifies the destination and that it opens in a new tab,
using localized visible text or a descriptive aria-label alongside the existing
copyright content. Keep the existing link target and security attributes
unchanged.

In `@app/features/nav-menu/index.ts`:
- Line 1: Update the nav-menu feature barrel in index.ts to export the
useNavMenu composable alongside NavMenu, using the composable’s existing module
path and preserving the current UI component export.

In `@app/shared/ui/logo/app-logo-wordmark.vue`:
- Around line 7-8: Update the two NuxtImg elements in the combined logo so only
one provides the translated common.logoAlt accessible name; set the other
image’s alt text to an empty decorative value. Preserve both images and their
existing dimensions within the NuxtLink.

In `@app/shared/ui/logo/app-logo.stories.ts`:
- Around line 1-14: Update the project dependency providing `@storybook-vue/nuxt`
to a Storybook 10-compatible release, ensuring its peer dependency accepts the
installed storybook@10.5.5; adjust the corresponding lockfile resolution and
preserve the existing AppLogo story integration.

---

Nitpick comments:
In `@app/shared/ui/logo/app-logo-wordmark.stories.ts`:
- Line 1: Update the AppLogoWordmark import in
app/shared/ui/logo/app-logo-wordmark.stories.ts at lines 1-1 to use the shared
UI barrel. Also update the AppWordmark import in
app/shared/ui/logo/app-wordmark.stories.ts at lines 1-1 to import from the same
barrel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 09969fce-fce8-4c82-891d-65c172b70965

📥 Commits

Reviewing files that changed from the base of the PR and between b2d87c8 and c37456a.

⛔ Files ignored due to path filters (1)
  • public/linkfolio-wordmark.svg is excluded by !**/*.svg
📒 Files selected for processing (18)
  • app/app.vue
  • app/features/auth-buttons/ui/auth-buttons.vue
  • app/features/copyright/ui/copyright-notice.vue
  • app/features/nav-menu/index.ts
  • app/features/nav-menu/model/use-nav-menu.ts
  • app/features/nav-menu/ui/nav-menu.vue
  • app/shared/ui/index.ts
  • app/shared/ui/logo/app-logo-wordmark.stories.ts
  • app/shared/ui/logo/app-logo-wordmark.vue
  • app/shared/ui/logo/app-logo.stories.ts
  • app/shared/ui/logo/app-logo.vue
  • app/shared/ui/logo/app-wordmark.stories.ts
  • app/shared/ui/logo/app-wordmark.vue
  • app/shared/ui/logo/index.ts
  • app/ui/app-footer.vue
  • app/ui/app-footer/footer-links.vue
  • app/ui/app-header.vue
  • app/ui/app-header/app-header.vue
💤 Files with no reviewable changes (4)
  • app/ui/app-footer/footer-links.vue
  • app/shared/ui/logo/app-logo.vue
  • app/ui/app-header/app-header.vue
  • app/features/auth-buttons/ui/auth-buttons.vue

Comment thread app/features/copyright/ui/copyright-notice.vue Outdated
Comment thread app/features/nav-menu/index.ts
Comment thread app/shared/ui/logo/app-logo-wordmark.vue Outdated
Comment on lines +1 to +14
import AppLogo from "./app-logo.vue";

import type { Meta, StoryObj } from "@storybook-vue/nuxt";

const meta: Meta<typeof AppLogo> = {
title: "Shared UI/Logo/AppLogo",
component: AppLogo
};

export default meta;

type Story = StoryObj<typeof AppLogo>;

export const Default: Story = {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

npm view `@storybook-vue/nuxt`@9.0.1 peerDependencies --json
npm view storybook@10.5.5 version

Repository: ebulgakov/linkfolio

Length of output: 896


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf 'Package manager files:\n'
git ls-files | rg '(^|/)(package(-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?)$' || true

printf '\nRelevant `@storybook-vue/nuxt/storybook` versions in package files:\n'
for f in $(git ls-files | rg '(^|/)(package(-lock)?\.json|pnpm-lock\.yaml|yarn\.lock)$' || true); do
  echo "--- $f"
  rg -n '"(`@storybook-vue/nuxt`|storybook)"|`@storybook-vue/nuxt`|storybook' "$f" || true
done

printf '\nLockfiles for `@storybook-vue/nuxt` package metadata:\n'
for f in $(git ls-files | rg '(^|/)pnpm-lock\.yaml$' || true); do
  echo "--- $f"
  sed -n '/@storybook-vue\/nuxt@9\.0\.1/,/^$/p; /node_modules\/@storybook-vue\/nuxt/,/^$/p; /@storybook-vue\/nuxt:/,/^[^:]|^$/p' "$f" | sed -n '1,160p' || true
done

Repository: ebulgakov/linkfolio

Length of output: 9860


Upgrade the Nuxt Storybook framework package.

@storybook-vue/nuxt: ^9.0.1 still resolves to @storybook-vue/nuxt@9.0.1, whose peerDependencies require storybook: ~9.0.5 instead of the installed storybook@10.5.5. Either add a Storybook 10-compatible Nuxt Storybook framework package or wait for the Nuxt Storybook provider to update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/shared/ui/logo/app-logo.stories.ts` around lines 1 - 14, Update the
project dependency providing `@storybook-vue/nuxt` to a Storybook 10-compatible
release, ensuring its peer dependency accepts the installed storybook@10.5.5;
adjust the corresponding lockfile resolution and preserve the existing AppLogo
story integration.

- Give GitHub copyright link an aria-label describing destination and new-tab behavior instead of relying on title alone
- Mark decorative wordmark image alt="" in AppLogoWordmark to avoid duplicate brand-name announcement
- Export useNavMenu from nav-menu feature barrel per FSD public API convention

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ebulgakov

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown

Claude finished @ebulgakov's task in 1m 52s —— View job


Review

  • Gather context (PR diff, CodeRabbit comments, CLAUDE.md conventions)
  • Review changed files against FSD conventions
  • Check CodeRabbit's flagged items for validity
  • Post review feedback

CodeRabbit's 4 actionable comments all check out as already fixed in commit 8d1fef9:

  • copyright-notice.vue — GitHub link now has aria-label="footer.githubLinkLabel"
  • nav-menu/index.ts — now exports useNavMenu alongside NavMenu
  • app-logo-wordmark.vue — second image now has alt="" (decorative)
  • The @storybook-vue/nuxt peer-dependency mismatch (needs storybook@~9.0.5, repo has 10.5.5) predates this PR — package.json isn't touched here, so it's not this PR's responsibility to fix, though it may still cause pnpm install peer-dep warnings.
  • The remaining nitpick (stories importing components directly instead of via the ~/shared/ui barrel) is debatable — Storybook stories conventionally import their subject component directly rather than through a barrel, so I'd leave this as-is.

Additional findings from my own pass

  1. app/features/nav-menu/ui/nav-menu.vue:2 — imports the composable via a deep path with an explicit .ts extension:

    import { useNavMenu } from "~/features/nav-menu/model/use-nav-menu.ts";

    Every other feature slice's UI component imports its composable through the slice's own barrel instead (login-form.vue~/features/login, signup-form.vue~/features/signup, locale-switcher.vue~/features/locale-switcher, auth-buttons.vue~/features/auth-buttons). Since index.ts now exports useNavMenu, this should be:

    import { useNavMenu } from "~/features/nav-menu";
  2. use-nav-menu.ts duplicates use-auth-buttons.ts's session-fetch logic — both composables independently implement the identical authClient.useSession customFetcher with the "auth-session" key and the SSR cookie-forwarding workaround. use-auth-buttons.ts has a detailed comment explaining why this custom fetcher is needed (better-auth/vue drops the cookie header otherwise); use-nav-menu.ts copies the behavior but not the comment, so the non-obvious reasoning now only lives in one of the two copies. Since AppHeader renders both AuthButtons and NavMenu, they'll share the fetch cache via the same key, but the logic itself is duplicated — worth extracting into a shared helper (e.g. app/shared/api) so a future fix to one doesn't silently miss the other.

  3. app/ui/app-header.vue:4import { AppLogoWordmark } from "~/shared/ui/"; has a trailing slash, inconsistent with other imports of the same barrel (~/shared/ui elsewhere). Cosmetic only.

  4. copyright-notice.vue — the <a> already carries class="text-caption ...", so the wrapping <span class="text-caption"> is redundant. Minor cleanup, not a bug.

None of these are blocking — the CodeRabbit-flagged items are resolved and the feature works as described. Items 1–2 are worth a follow-up if you want to tighten consistency/DRY.

- nav-menu.vue imports useNavMenu through the feature's own barrel instead of a deep path
- app-header.vue import of shared/ui barrel drops trailing slash
- copyright-notice.vue drops redundant wrapping span
- Extracted the duplicated authClient.useSession customFetcher (SSR cookie forwarding workaround) out of use-auth-buttons.ts and use-nav-menu.ts into a shared useAuthSession() in app/shared/api, so the non-obvious reasoning lives in one place

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/shared/api/use-session.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the ~/shared/api barrel for this shared dependency.

app/shared/api/use-session.ts imports useAuth through ./use-auth. Route shared API imports through the segment barrel. If a self-import creates a cycle, adjust the barrel structure instead of keeping a deep import.

As per coding guidelines: Files matching app/shared/{api,ui,lib}/**/*.{ts,vue} must import shared modules through their segment barrels.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/shared/api/use-session.ts` at line 1, Update use-session.ts to import
useAuth through the ~/shared/api segment barrel instead of the relative
./use-auth path, and adjust the barrel structure if needed to avoid a
self-import cycle.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/shared/api/use-session.ts`:
- Line 7: Use the shared auth-client selection pattern in
app/shared/api/use-session.ts at line 7 and
app/features/auth-buttons/model/use-auth-buttons.ts at line 4 so browser-side
session reads and sign-out reuse one client instance, while preserving the
existing SSR/client distinction.
- Around line 9-14: Update the useAuthSession callback passed to
authClient.useSession so absolute URLs are normalized to their relative pathname
and query before being forwarded to useFetch, preserving the existing request
headers and auth-session key. Ensure SSR logged-in session data and client
hydration continue receiving the session correctly.

---

Nitpick comments:
In `@app/shared/api/use-session.ts`:
- Line 1: Update use-session.ts to import useAuth through the ~/shared/api
segment barrel instead of the relative ./use-auth path, and adjust the barrel
structure if needed to avoid a self-import cycle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ff0a3e06-0a37-4dfb-ab24-a42921eb40d3

📥 Commits

Reviewing files that changed from the base of the PR and between c37456a and b19a7b3.

📒 Files selected for processing (11)
  • app/features/auth-buttons/model/use-auth-buttons.ts
  • app/features/copyright/ui/copyright-notice.vue
  • app/features/nav-menu/index.ts
  • app/features/nav-menu/model/use-nav-menu.ts
  • app/features/nav-menu/ui/nav-menu.vue
  • app/shared/api/index.ts
  • app/shared/api/use-session.ts
  • app/shared/ui/logo/app-logo-wordmark.vue
  • app/ui/app-header.vue
  • i18n/locales/en.json
  • i18n/locales/ru.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/features/nav-menu/ui/nav-menu.vue
  • app/ui/app-header.vue
  • app/features/copyright/ui/copyright-notice.vue
  • app/features/nav-menu/index.ts
  • app/shared/ui/logo/app-logo-wordmark.vue

// it never forwards the client's own fetchOptions.headers, so the cookie useAuth() attaches
// for SSR is otherwise dropped here and the server-side session lookup comes back unauthenticated.
export async function useAuthSession() {
const authClient = useAuth();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -C 5 'createAuthClient|export .*authClient|useAuth\(\)|useAuthSession|useSession' app/shared/api app/features

Repository: ebulgakov/linkfolio

Length of output: 7125


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf 'Files changed/modified:\n'
git diff --stat || true

printf '\nRelevant source files:\n'
for f in \
  app/shared/api/auth-client.ts \
  app/shared/api/use-auth.ts \
  app/shared/api/use-session.ts \
  app/features/auth-buttons/model/use-auth-buttons.ts \
  app/features/home-landing/model/use-landing-hero.ts \
  app/features/collections/ui/collections-list.vue \
  app/features/auth-buttons/ui/*.vue \
  app/shared/api/index.ts
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
    cat -n "$f"
  fi
done

printf '\nPackage / Better Auth version references:\n'
for f in package.json pnpm-lock.yaml yarn.lock package-lock.json bun.lockb bun.lock; do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    rg -n "better-auth|vue" "$f" | head -50
  fi
done

echo "===== git status (short) ====="
git status --short

Repository: ebulgakov/linkfolio

Length of output: 16209


🌐 Web query:

better-auth vue createAuthClient singleton client useSession SSR cookies import.meta.client

💡 Result:

To use Better Auth with Vue (specifically in a Nuxt context), follow these best practices for client creation, session management, and SSR. Client Creation and Singleton The auth client is typically created once as a singleton for general use, but you may need request-scoped clients during SSR to ensure cookies are forwarded correctly [1][2][3]. Create a standard client for general use in a dedicated file: // lib/auth-client.ts import { createAuthClient } from "better-auth/vue"; export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // Required if auth server is on a different domain }); export const { signIn, signUp, signOut, useSession } = authClient; SSR and Cookie Forwarding By default, the standard authClient does not forward cookies during SSR. Use one of these two approaches to handle authenticated requests server-side [2][3]: Approach A: Guard with import.meta.client Only call client methods that require authentication (like listAccounts or profile) on the client side [3]. <script setup lang="ts"> import { authClient } from "/lib/auth-client"; const data = ref; onMounted(async => { if (import.meta.client) { const res = await authClient.listAccounts; data.value = res.data; } }); </script> Approach B: Request-Scoped Client For SSR-native data fetching, create a helper composable that manually forwards cookies [2][3]. // app/composables/useAuth.ts import { createAuthClient } from "better-auth/vue"; export function useAuth { const headers = import.meta.server? useRequestHeaders(["cookie"]): undefined; return createAuthClient({ fetchOptions: { headers }, }); } // In your page/component const { data } = await useAsyncData("data", => useAuth.listAccounts.then((res) => res.data)); Session Management For session state, use the built-in useSession hook. In Nuxt, passing useFetch to useSession allows it to handle SSR session hydration properly by including the request cookies [2][4]. <script setup lang="ts"> import { authClient } from "/lib/auth-client"; // This fetches the session on the server and hydrates it on the client const { data: session } = await authClient.useSession(useFetch); </script> Note: If you encounter hydration mismatches or session issues due to absolute URLs in useFetch, use a relative path wrapper to ensure Nuxt correctly forwards cookies to the local API endpoint [4].

Citations:


Use one browser-side authClient for session reads and sign-out.

useAuth() creates a new Better Auth client on each call. In app/features/auth-buttons/model/use-auth-buttons.ts, useAuthSession() resolves session data through one client while authClient.signOut() signs out through a different client. Use the shared client-select pattern for both SSR and client-side auth work.

  • app/features/auth-buttons/model/use-auth-buttons.ts#L4 calls useAuth() during browser execution.
  • app/shared/api/use-session.ts#L7 also calls useAuth() from browser-side auth usage.
📍 Affects 2 files
  • app/shared/api/use-session.ts#L7-L7 (this comment)
  • app/features/auth-buttons/model/use-auth-buttons.ts#L4-L4
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/shared/api/use-session.ts` at line 7, Use the shared auth-client
selection pattern in app/shared/api/use-session.ts at line 7 and
app/features/auth-buttons/model/use-auth-buttons.ts at line 4 so browser-side
session reads and sign-out reuse one client instance, while preserving the
existing SSR/client distinction.

Source: Coding guidelines

Comment on lines +9 to +14
const { data: session } = await authClient.useSession((url, opts) =>
useFetch(url, {
...opts,
key: "auth-session",
headers: import.meta.server ? useRequestHeaders(["cookie"]) : undefined
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -C 6 'baseURL|useSession|useFetch\(url|auth-session' app/shared/api

Repository: ebulgakov/linkfolio

Length of output: 1836


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '--- relevant files ---\n'
wc -l app/shared/api/use-auth.ts app/shared/api/use-session.ts
cat -n app/shared/api/use-auth.ts app/shared/api/use-session.ts

printf '\n--- imports/usages of useAuthSession/useAuth ---\n'
rg -n 'useAuthSession|useAuth\(|createAuthClient|better-auth' app --glob '*.{ts,vue,tsx}'

printf '\n--- config mentions of auth/baseURL/api ---\n'
rg -n -C 4 'better-auth|baseURL|api.*base|auth' nuxt.config.* app 2>/dev/null || true

printf '\n--- package better-auth version ---\n'
node - <<'JS'
const fs = require('fs');
for (const f of ['package.json','package-lock.json','pnpm-lock.yaml','yarn.lock']) {
  if (fs.existsSync(f)) console.log(f);
}
try {
  const p = JSON.parse(fs.readFileSync('package.json','utf8'));
  console.log({ dependencies: p.dependencies?.['better-auth'], devDependencies: p.devDependencies?.['better-auth'] });
} catch (e) {}
JS

Repository: ebulgakov/linkfolio

Length of output: 248


🌐 Web query:

better-auth Nuxt integration useSession Nuxt payload reuse hydration absolute baseURL

💡 Result:

To integrate better-auth in Nuxt with proper session hydration and address potential issues with absolute URLs and cookie forwarding, use the following patterns. 1. Recommended SSR Pattern Pass Nuxt's useFetch to useSession to ensure session data is fetched during SSR and correctly hydrated on the client [1][2]. <script setup lang="ts"> import { authClient } from "~~/lib/auth-client"; const { data: session } = await authClient.useSession(useFetch); </script> 2. Handling Absolute baseURL Issues If you have a baseURL configured in your auth client (e.g., for cross-domain usage or specific environment requirements), it may cause Nuxt's useFetch to treat the request as cross-origin, which causes it to drop cookies during SSR [3][4]. To fix this, wrap useFetch to force relative paths [3][4]: import { useFetch } from '#app'; const relativeFetch = ((url: string, opts?: any) => { try { if (url.startsWith('http')) url = new URL(url).pathname; } catch {} return useFetch(url, opts); }) as any; const { data: session } = await authClient.useSession(relativeFetch); 3. Using hydrateSession For scenarios where you need to explicitly seed the client with an initial session to avoid loading flashes or to handle complex SSR data passing, you can use the hydrateSession method [5]. authClient.hydrateSession(initialSession); const { data: session } = authClient.useSession; 4. Configuration Best Practices - Base URL Priority: Ensure your baseURL is correctly defined [6][7]. In Nuxt, the recommended approach is setting NUXT_PUBLIC_SITE_URL or using runtimeConfig.public.siteUrl [8]. Relying on auto-inference is not recommended for production [6][7]. - Cookie Forwarding: Always ensure that you are using relative paths or that your setup supports cross-origin cookie credentials if you must use absolute URLs [3][4]. - Avoid Configuration Drift: If using a custom basePath (e.g., /api/v1/auth), ensure that the Better Auth server config, the API route path, and the auth client's baseURL all match precisely to avoid 404s or session resolution failures [9].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- relevant files ---'
wc -l app/shared/api/use-auth.ts app/shared/api/use-session.ts
cat -n app/shared/api/use-auth.ts app/shared/api/use-session.ts

echo
echo '--- imports/usages of useAuthSession/useAuth ---'
rg -n 'useAuthSession|useAuth\(|createAuthClient|better-auth' app --glob '*.{ts,vue,tsx}' || true

echo
echo '--- config mentions of auth/baseURL/api ---'
rg -n -C 4 'better-auth|baseURL|api.*base|auth' nuxt.config.* app 2>/dev/null || true

echo
echo '--- package better-auth version ---'
node - <<'JS'
const fs = require('fs');
for (const f of ['package.json','package-lock.json','pnpm-lock.yaml','yarn.lock']) {
  if (fs.existsSync(f)) console.log(f);
}
try {
  const p = JSON.parse(fs.readFileSync('package.json','utf8'));
  console.log({ dependencies: p.dependencies?.['better-auth'], devDependencies: p.devDependencies?.['better-auth'] });
} catch (e) {}
JS

Repository: ebulgakov/linkfolio

Length of output: 40062


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- root config candidates ---'
git ls-files | rg '(^|/)nuxt\.config\.|(^|/)app\.config\.|(^|/)better-auth|auth' | sed 's#^\./##'

echo
echo '--- content around config/auth files ---'
for f in $(git ls-files | rg '(^|/)(nuxt.config|app.config|better-auth)\.(ts|js|mjs|mts|mjs)$' || true); do
  echo "### $f"
  wc -l "$f"
  cat -n "$f"
done

Repository: ebulgakov/linkfolio

Length of output: 1798


🌐 Web query:

Better Auth Nuxt app.config.ts authURL baseURL useSession absolute URL issue

💡 Result:

In Better Auth Nuxt applications, the issue where useSession(useFetch) fails during SSR often stems from the use of an absolute URL for the baseURL in the client configuration [1][2][3]. When an absolute URL (e.g., http://localhost:3000) is provided, Nuxt's useFetch treats it as a cross-origin request, which prevents the automatic forwarding of cookies during server-side rendering [1][2][3]. Consequently, the session request is made without authentication credentials, resulting in a null session [1][2]. To resolve this, follow these recommended practices for single-domain Nuxt applications: 1. Avoid Absolute URLs on the Client: Do not set a baseURL on the client-side Better Auth instance if your application and auth handler share the same domain [1][2]. Omitting it forces requests to be relative, allowing Nuxt to correctly forward cookies during SSR [1][3]. // lib/auth-client.ts export const authClient = createAuthClient; // Do not set baseURL here 2. Configure Base URL on the Server Only: Ensure the baseURL is explicitly defined in your server-side Better Auth configuration, not on the client [1][2]. // lib/auth.ts export const auth = betterAuth({ baseURL: process.env.BETTER_AUTH_URL || 'http://localhost:3000', basePath: '/api/auth', //... }); 3. Workaround for Absolute Paths: If you absolutely must use a baseURL in the client, you can wrap the useFetch call to strip the origin, ensuring the URL remains relative for the server-side request [1][2][3]: import { useFetch } from '#app'; const relativeFetch = ((url: string, opts?: any) => { try { if (url.startsWith('http')) url = new URL(url).pathname; } catch {} return useFetch(url, opts); }) as any; const { data: session } = await authClient.useSession(relativeFetch); 4. Explicitly Handle Responses: Ensure your Nuxt catch-all handler correctly forwards headers using sendWebResponse to maintain session state [1][2]. // server/api/auth/[...all].ts export default defineEventHandler(async (event) => { const response = await auth.handler(toWebRequest(event)); return sendWebResponse(event, response); }); If you are using a Nuxt layer or a more complex architecture, ensure you are not inadvertently setting NUXT_PUBLIC_BETTER_AUTH_URL or NUXT_PUBLIC_AUTH_URL in your environment, as these can silently inject an absolute baseURL into your client config [1][2][3].

Citations:


Use a relative URL in useAuthSession’s useFetch call.

useAuth() sets baseURL to useRequestURL().origin, so authClient.useSession() can pass an absolute URL here. That makes Nuxt treat the SSR session lookup as a cross-origin request and can drop the request body/cookie, leaving hydration with an undefined session. Test the logged-in SSR + hydration path, and normalize absolute URLs to their pathname/query before forwarding them to useFetch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/shared/api/use-session.ts` around lines 9 - 14, Update the useAuthSession
callback passed to authClient.useSession so absolute URLs are normalized to
their relative pathname and query before being forwarded to useFetch, preserving
the existing request headers and auth-session key. Ensure SSR logged-in session
data and client hydration continue receiving the session correctly.

@ebulgakov
ebulgakov merged commit b2fd7a3 into main Aug 3, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant