---
title: "Shared Packages"
description: "Build shared UI components, utilities, and configuration packages used across all applications."
canonical_url: "https://vercel.com/academy/microfrontends-on-vercel/shared-packages-introduction"
md_url: "https://vercel.com/academy/microfrontends-on-vercel/shared-packages-introduction.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-09T09:43:07.390Z"
content_type: "lesson"
course: "microfrontends-on-vercel"
course_title: "Microfrontends on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Shared Packages

# Shared Packages

Some things should stay unified across apps: headers, footers, design systems, auth logic. Shared packages handle this.

## Outcome

Build a complete shared UI package with Header and Footer components, plus a utils package for shared authentication helpers.

## Why Shared Packages

Without shared packages:

```
apps/marketing/components/Header.tsx  ← Copy 1
apps/docs/components/Header.tsx       ← Copy 2 (might drift)
apps/dashboard/components/Header.tsx  ← Copy 3 (definitely drifts)
```

With shared packages:

```
packages/ui/src/header.tsx            ← Single source of truth
apps/*/app/layout.tsx                 ← All import from @acme/ui
```

When you update the Header, all apps get the update on their next build.

## Fast Track

1. Expand the UI package with Header and Footer
2. Create a utils package for auth helpers
3. Use shared components in all app layouts

## Hands-on Exercise 2.4

Build complete shared packages for Acme Platform.

### Part 1: Expand the UI Package

Update `packages/ui/src/header.tsx` with improved styling and navigation:

```tsx title="packages/ui/src/header.tsx"
export function Header() {
  return (
    <header className="h-16 border-b bg-white px-6 flex items-center justify-between">
      <div className="flex items-center gap-8">
        <a href="/" className="font-bold text-xl">
          Acme
        </a>
        <nav className="hidden md:flex items-center gap-6">
          <a href="/" className="text-gray-600 hover:text-gray-900">
            Home
          </a>
          <a href="/docs" className="text-gray-600 hover:text-gray-900">
            Docs
          </a>
          <a href="/pricing" className="text-gray-600 hover:text-gray-900">
            Pricing
          </a>
        </nav>
      </div>
      <div className="flex items-center gap-4">
        <a
          href="/app"
          className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
        >
          Dashboard
        </a>
      </div>
    </header>
  );
}
```

Create `packages/ui/src/footer.tsx`:

```tsx title="packages/ui/src/footer.tsx"
export function Footer() {
  return (
    <footer className="border-t bg-gray-50 px-6 py-8">
      <div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between gap-8">
        <div>
          <p className="font-bold text-lg">Acme Platform</p>
          <p className="text-gray-600 text-sm mt-1">
            The everything app for your business.
          </p>
        </div>
        <div className="flex gap-12">
          <div>
            <p className="font-semibold mb-2">Product</p>
            <nav className="flex flex-col gap-1 text-sm text-gray-600">
              <a href="/pricing" className="hover:text-gray-900">Pricing</a>
              <a href="/docs" className="hover:text-gray-900">Documentation</a>
            </nav>
          </div>
          <div>
            <p className="font-semibold mb-2">Company</p>
            <nav className="flex flex-col gap-1 text-sm text-gray-600">
              <a href="/about" className="hover:text-gray-900">About</a>
              <a href="/contact" className="hover:text-gray-900">Contact</a>
            </nav>
          </div>
        </div>
      </div>
      <div className="max-w-6xl mx-auto mt-8 pt-4 border-t text-center text-sm text-gray-500">
        &copy; {new Date().getFullYear()} Acme Inc. All rights reserved.
      </div>
    </footer>
  );
}
```

Update `packages/ui/src/index.ts`:

```ts title="packages/ui/src/index.ts"
export { Header } from "./header";
export { Footer } from "./footer";
```

### Part 2: Create the Utils Package

Create a shared utilities package for authentication and other helpers.

Create `packages/utils/package.json`:

```json title="packages/utils/package.json"
{
  "name": "@acme/utils",
  "version": "0.0.0",
  "private": true,
  "exports": {
    ".": {
      "types": "./src/index.ts",
      "default": "./src/index.ts"
    }
  },
  "devDependencies": {
    "typescript": "^5.7.0"
  }
}
```

Create `packages/utils/tsconfig.json`:

```json title="packages/utils/tsconfig.json"
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}
```

Create `packages/utils/src/auth.ts`:

```ts title="packages/utils/src/auth.ts"
import type { NextRequest } from "next/server";

export interface AuthToken {
  userId: string;
  email: string;
  exp: number;
}

export function getAuthToken(request: NextRequest): string | undefined {
  return request.cookies.get("auth-token")?.value;
}

export function isAuthenticated(request: NextRequest): boolean {
  const token = getAuthToken(request);
  if (!token) return false;
  // In a real app, verify the JWT here
  return true;
}

export function decodeToken(token: string): AuthToken | null {
  try {
    // In a real app, use a proper JWT library
    const payload = JSON.parse(atob(token.split(".")[1]));
    return payload as AuthToken;
  } catch {
    return null;
  }
}
```

Create `packages/utils/src/index.ts`:

```ts title="packages/utils/src/index.ts"
export { getAuthToken, isAuthenticated, decodeToken } from "./auth";
export type { AuthToken } from "./auth";
```

### Part 3: Add UI Package as Dependency

Add `@acme/ui` to all three apps. Update each `package.json`:

**apps/marketing/package.json:**

```json title="apps/marketing/package.json" {5}
{
  "dependencies": {
    "@acme/ui": "workspace:*",
    "@vercel/microfrontends": "^2.3.0",
    "next": "16.1.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}
```

**apps/docs/package.json:**

```json title="apps/docs/package.json" {5}
{
  "dependencies": {
    "@acme/ui": "workspace:*",
    "@vercel/microfrontends": "^2.3.0",
    "next": "16.1.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}
```

**apps/dashboard/package.json:**

```json title="apps/dashboard/package.json" {5}
{
  "dependencies": {
    "@acme/ui": "workspace:*",
    "@vercel/microfrontends": "^2.3.0",
    "next": "16.1.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}
```

Run `pnpm install` to link the workspace dependencies.

### Part 4: Configure Tailwind to Scan Shared Packages

Tailwind only scans files in the `content` array. Without this step, shared component styles won't work.

Update `tailwind.config.ts` in **all three apps**:

```ts title="apps/marketing/tailwind.config.ts" {6}
import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "../../packages/ui/src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

export default config;
```

Apply the same change to `apps/docs/tailwind.config.ts` and `apps/dashboard/tailwind.config.ts`.

If shared component styles don't appear, you probably forgot to add the packages path to Tailwind's `content` array.

### Part 5: Update Layouts to Use Header and Footer

Update `apps/marketing/app/layout.tsx`:

```tsx title="apps/marketing/app/layout.tsx"
import type { Metadata } from "next";
import { Header, Footer } from "@acme/ui";
import "./globals.css";

export const metadata: Metadata = {
  title: "Acme Platform",
  description: "The everything app for your business",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="min-h-screen bg-white flex flex-col">
        <Header />
        <main className="flex-1">{children}</main>
        <Footer />
      </body>
    </html>
  );
}
```

Update `apps/docs/app/layout.tsx`:

```tsx title="apps/docs/app/layout.tsx"
import type { Metadata } from "next";
import { Header, Footer } from "@acme/ui";
import "./globals.css";

export const metadata: Metadata = {
  title: "Acme Docs",
  description: "Documentation for Acme Platform",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="min-h-screen bg-white flex flex-col">
        <Header />
        <main className="flex-1">{children}</main>
        <Footer />
      </body>
    </html>
  );
}
```

Update `apps/dashboard/app/layout.tsx`:

```tsx title="apps/dashboard/app/layout.tsx"
import type { Metadata } from "next";
import { Header } from "@acme/ui";
import "./globals.css";

export const metadata: Metadata = {
  title: "Acme Dashboard",
  description: "Manage your Acme Platform",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="min-h-screen bg-gray-50">
        <Header />
        <main className="p-8">{children}</main>
      </body>
    </html>
  );
}
```

### Part 6: Install Dependencies and Test

```bash
pnpm install
pnpm dev
```

## Try It

1. Visit `localhost:3024`
2. Navigate between apps - the Header and Footer should be consistent
3. Check that Tailwind styles are applied (proper spacing, colors, hover states)

## Commit

```bash
git add -A
git commit -m "feat: add shared Header and Footer components"
```

## Done-When

- [ ] Header component renders in all three apps
- [ ] Footer component renders in marketing and docs apps
- [ ] Tailwind styles are applied correctly (not unstyled)
- [ ] Consistent look and feel across all apps

## Package Versioning Strategy

In this monorepo setup, packages use `workspace:*` for dependencies:

```json
"@acme/ui": "workspace:*"
```

This means "use the version in this workspace." All apps always use the same version.

For published packages (external to monorepo), you'd want explicit version pinning and a versioning strategy. But for internal packages in a monorepo, `workspace:*` keeps everything in sync.

## What's Next

Deploy to Vercel and see routing work in production with fallback behavior.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
