Fityra/Blog
ArticlesBuild in PublicDeep Divesfityra.app ↗
Fityra/Blog

Technical deep dives and lessons learned building the Fityra ecosystem.

RSSSitemap

Blog

  • All Articles
  • Build in Public
  • Technical Deep Dives
  • Product Updates

Ecosystem

  • fityra.app ↗
  • SaaSLift ↗
  • Extensions ↗

© 2026 Edson Mark. All rights reserved.

Chrome Extensions

Building an App-Store UI for My Chrome Extensions with Next.js

I did not want my Chrome extensions to live only as scattered store listings, so I built a small marketplace-style site in Next.js with search, product cards, SEO, and honest install paths.

10 min read
chrome extensionsnext.jsapp store uiproduct designseosolo developer
Building an App-Store UI for My Chrome Extensions with Next.js

At some point, having multiple Chrome extensions starts to feel less like a portfolio and more like a shelf full of tools with no labels.

That was the problem I ran into.

I had been building small browser products: meeting reminders, permission monitoring, license utilities, and a few experiments that were still somewhere between "useful" and "I need to clean this up before anyone sees it." Each one had its own purpose. Each one made sense in isolation.

But together, they did not feel like a product system.

The Chrome Web Store is where users install extensions, and it matters. I still want every extension to have a good listing there. Google has a whole Chrome Web Store developer area for publishing, policies, listing quality, images, reviews, and analytics.

But I did not want the Chrome Web Store to be the only front door.

I wanted a site where someone could see all my extensions together and understand the pattern:

  • These are small, focused browser tools.
  • They are built by one developer.
  • They solve specific daily problems.
  • Some are free, some have pro features.
  • Each extension has a clear reason to exist.

That is why I built an app-store style UI for my Chrome extensions in Next.js.

Not because I wanted to compete with the real Chrome Web Store.

Because I wanted a better home base for my own products.

The first version looked too much like a marketplace

My first design mistake was trying to make the page look too official.

I added too many marketplace patterns:

  • Big ratings
  • Review counts
  • Download counts
  • Complex category filters
  • Compatibility badges
  • "Featured" ribbons
  • Multiple card actions
  • A dense comparison layout

It looked believable for about five seconds.

Then it felt fake.

The problem was not the UI quality. The problem was honesty.

I was not building a marketplace with thousands of extensions and millions of users. I was building a personal extension hub. If I showed empty review counts and invented-looking metrics, the page would technically look more complete but emotionally feel less trustworthy.

So I stripped the UI down.

The card only needed to answer four questions:

  1. What is this extension?
  2. What problem does it solve?
  3. Is it free, paid, or freemium?
  4. Where do I go next?

That became the whole design direction.

Simple cards. Clear names. Short descriptions. Obvious install or details action. Enough filtering to be useful, but not so much that the page pretends to be bigger than it is.

The smaller the product catalog, the more honest the interface has to be.

The data model came before the UI

Before building the cards, I wrote down the shape of an extension.

This helped more than I expected. When I only designed visually, every card became a one-off. When I defined the data first, the page started to feel like a real product surface.

My extension object looked roughly like this:

export type ExtensionStatus = 'live' | 'beta' | 'private'
export type PricingType = 'free' | 'freemium' | 'paid'
 
export interface ExtensionListing {
  slug: string
  name: string
  tagline: string
  description: string
  category: string
  status: ExtensionStatus
  pricing: PricingType
  chromeWebStoreUrl?: string
  landingPageUrl: string
  icon: string
  permissions: string[]
  highlights: string[]
}

The fields look ordinary, but each one prevents a vague UI decision later.

status tells me whether to show an install button or a waitlist-style action.

pricing keeps me from hiding monetization until the last click.

permissions lets me explain trust and privacy in plain language.

highlights gives each card a few scannable reasons to care.

The mistake I made in the past was treating the website as decoration after the product was built. This time, the website forced me to clarify the product.

That is a good pressure.

If I cannot describe an extension cleanly in a data object, I probably do not understand its positioning yet.

The card had to be quiet

A product card can easily become noisy.

There is the name, tagline, description, icon, category, price, status, install action, details action, maybe screenshots, maybe permissions, maybe "new" or "featured" badges.

If everything gets equal weight, nothing is readable.

I ended up thinking of the card as a small decision surface, not a brochure.

The top area is identity:

function ExtensionCard({ extension }: { extension: ExtensionListing }) {
  return (
    <article className="rounded-lg border border-zinc-200 bg-white p-5">
      <div className="flex items-start gap-4">
        <img
          src={extension.icon}
          alt=""
          className="h-12 w-12 rounded-xl"
        />
        <div>
          <h2 className="text-base font-semibold text-zinc-950">
            {extension.name}
          </h2>
          <p className="mt-1 text-sm text-zinc-600">
            {extension.tagline}
          </p>
        </div>
      </div>
    </article>
  )
}

Then the lower area is the decision:

<div className="mt-5 flex items-center justify-between gap-3">
  <span className="rounded-full border px-2.5 py-1 text-xs">
    {extension.pricing}
  </span>
 
  <a
    href={extension.landingPageUrl}
    className="rounded-md bg-zinc-950 px-3 py-2 text-sm text-white"
  >
    View details
  </a>
</div>

I know this is not a fancy component.

That is the point.

The card should not be more interesting than the extension. It should make the extension understandable.

I avoided ratings on purpose

This felt like a small choice, but it mattered.

Most app-store UIs revolve around ratings. Stars are familiar. Users understand them quickly. They also add social proof.

But I did not have enough real review data to make ratings meaningful across my own site. Pulling store ratings dynamically would add complexity. Manually copying numbers would become stale. Showing zero or one review would make some products look abandoned even if they were simply new.

So I left ratings out.

Instead, I used more controllable signals:

  • Category
  • Pricing
  • Status
  • What the extension does
  • What permission-sensitive behavior it needs

This made the page feel less like a fake marketplace and more like a curated product shelf.

That distinction sounds small, but users feel it.

When a solo developer site overperforms visually but underperforms honestly, it creates suspicion. I would rather have a simpler page that feels true.

Search needed to work like memory, not like Google

The search bar was not there for broad discovery. I only had a handful of extensions.

The real use case was memory.

Someone might remember:

  • "the meeting one"
  • "permission checker"
  • "license key"
  • "calendar reminder"
  • "privacy"

So the search should match names, taglines, categories, and highlights.

For a small local list, I do not need a search backend. I can filter in memory:

function searchExtensions(items: ExtensionListing[], query: string) {
  const normalized = query.trim().toLowerCase()
  if (!normalized) return items
 
  return items.filter((item) => {
    const haystack = [
      item.name,
      item.tagline,
      item.description,
      item.category,
      item.pricing,
      ...item.highlights,
      ...item.permissions,
    ]
      .join(' ')
      .toLowerCase()
 
    return haystack.includes(normalized)
  })
}

This is not advanced search. It is not supposed to be.

The site is small enough that simple substring matching is a strength. It is predictable, fast, and easy to explain by behavior. If the catalog grows later, I can move to Fuse.js or a hosted search index.

But I did not want to start there.

Small systems deserve small machinery until they prove otherwise.

Filters are useful only when they reduce doubt

I originally added too many filters.

I had filters for:

  • Category
  • Pricing
  • Status
  • Permission level
  • Browser compatibility
  • Update recency
  • Product maturity

It was too much.

Filters are only helpful when users already understand the choices. If the filter list is longer than the product list, the UI has become self-important.

I kept three:

  • Category
  • Pricing
  • Status

Those are the filters that answer normal user questions.

"Is there a productivity extension?"

"Is this free?"

"Can I install it now?"

Everything else can live on the product detail page.

That became one of the design rules for the whole site:

The listing page is for choosing. The detail page is for understanding.

Once I made that separation, the UI got much easier.

The detail page does the trust work

For a Chrome extension, the detail page cannot just be a marketing page.

Extensions ask for browser permissions. Even harmless permissions can sound strange if the user has never thought about how extensions work. So each extension page needs to explain:

  • What the extension does
  • Why it needs its permissions
  • What it does not do
  • Whether data leaves the browser
  • What the free and paid limits are
  • How to uninstall or disable it

That last point is underrated.

A good extension site should not make users feel trapped. If the user knows they can remove the extension easily, the install feels safer.

This is the kind of trust writing that does not fit well inside a tiny Chrome Web Store summary. It fits better on a product page you control.

So the app-store UI is not only about discovery. It is also a trust layer between a quick listing and the real install.

Next.js was useful because every extension needs its own URL

I built this in Next.js because the content naturally maps to pages.

There is a listing page:

/extensions

And each extension gets a detail page:

/extensions/meeting-plane-reminder
/extensions/extension-permission-monitor
/extensions/license-key-manager

That structure is good for users and good for SEO.

The detail page can generate metadata from the same extension object:

export function generateMetadata({ params }: PageProps) {
  const extension = getExtensionBySlug(params.slug)
 
  return {
    title: `${extension.name} - Chrome Extension`,
    description: extension.description,
    alternates: {
      canonical: `/extensions/${extension.slug}`,
    },
    openGraph: {
      title: extension.name,
      description: extension.tagline,
      images: [extension.ogImage],
    },
  }
}

This is why I like keeping product data structured. The same source can power:

  • Cards
  • Detail pages
  • Metadata
  • JSON-LD
  • Search
  • Sitemap entries

When content is structured, SEO becomes less mystical. It becomes a consequence of clean data and clear pages.

Next.js has its own docs for metadata and OG images, and I kept those nearby while wiring this up. I did not want the page to look good in the browser but fall apart when shared in search results or social previews.

The install button needed a fallback

The obvious call to action is "Add to Chrome."

But not every extension is always in the same state.

Some are live. Some are waiting for review. Some are private while I test licensing. Some have a landing page before the store link.

So I made the action depend on status:

function getPrimaryAction(extension: ExtensionListing) {
  if (extension.status === 'live' && extension.chromeWebStoreUrl) {
    return {
      label: 'Add to Chrome',
      href: extension.chromeWebStoreUrl,
    }
  }
 
  if (extension.status === 'beta') {
    return {
      label: 'View beta details',
      href: extension.landingPageUrl,
    }
  }
 
  return {
    label: 'View details',
    href: extension.landingPageUrl,
  }
}

This prevents a surprisingly common product-site problem: buttons that promise more than the product can currently deliver.

If an extension is not public yet, the UI should say that. If the Chrome Web Store listing is not ready, the page should not pretend it is.

Trust is not only built by what you show. It is built by what you refuse to fake.

I added JSON-LD, but kept it boring

For SEO, I wanted each extension page to describe itself as software.

The useful schema type here is SoftwareApplication. I keep it straightforward:

const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'SoftwareApplication',
  name: extension.name,
  applicationCategory: 'BrowserApplication',
  operatingSystem: 'Chrome',
  description: extension.description,
  offers: {
    '@type': 'Offer',
    price: extension.pricing === 'free' ? '0' : extension.startingPrice,
    priceCurrency: 'USD',
  },
}
 
return (
  <script
    type="application/ld+json"
    dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
  />
)

I do not try to stuff every possible field into structured data.

The goal is not to trick search engines. The goal is to make the page easier to understand.

That is my general SEO rule now:

Make the page legible to humans first, then make the same facts legible to machines.

If those two versions disagree, the page has a trust problem.

The homepage section had to stay small

After building the extension listing page, I was tempted to make a big homepage section for it.

Big hero. Giant screenshot. "Featured Chrome extension." Lots of polish.

I tried that. It made the main site feel like it had two competing products fighting for the first screen.

So I pulled back.

The extension hub can be strong on its own route. The homepage only needs to point to it clearly. Not every product deserves the whole front page every day.

This was a good reminder for me as a solo builder:

Distribution surfaces need hierarchy.

If everything is featured, nothing is.

The app-store UI helped me organize the extensions, but it did not need to take over the entire Fityra site.

What I learned from the Chrome Web Store itself

The Chrome Web Store has responsibilities my site does not have.

It has to support discovery, reviews, policy compliance, publishing, screenshots, privacy disclosures, category browsing, and user trust at massive scale.

My site has a different job.

It can be more opinionated. It can explain why I built each extension. It can connect products together. It can show the human logic behind them.

That is the advantage of owning the product site.

The official store listing is where users install.

The product site is where users understand.

Once I separated those jobs, the whole experience became clearer.

The final page felt smaller, and that made it better

The version I liked most was not the most impressive version.

It was the one that felt easiest to scan.

A simple top bar. Search. A few filters. A grid of honest cards. Detail pages that explain the product and its permissions. Clear install paths. Metadata that matches the page content. No fake review ecosystem. No inflated marketplace theater.

It looked less like a huge app store and more like a well-organized workbench.

That is closer to the truth.

And the truth sells better than decoration when the products are small.

The checklist I would use again

If I build another product hub, I will start with this checklist:

  • Can each product be described in one sentence?
  • Does every card answer what it does, who it is for, and what to do next?
  • Are the filters fewer than the products?
  • Are ratings, badges, and metrics real enough to show?
  • Does every product have a stable URL?
  • Does metadata come from the same structured source as the UI?
  • Does the install button reflect the real product status?
  • Does the detail page explain permissions and privacy in plain language?
  • Does the page help users choose, or does it mostly show off?

That last question stings a little, which is why it is useful.

As builders, we can get attached to impressive UI. But users do not arrive hoping to admire our component system. They arrive trying to decide whether a tool is worth their time and trust.

The job of an app-store UI is not to look like an app store.

The job is to make choosing feel easy.

References

  • Chrome Web Store developer docs
  • Next.js metadata and OG images
  • Next.js Image component

View Chrome Extensions

Productivity-focused Chrome extensions built for developers and power users.

Browse extensions →
Share
PreviousInjecting a UI Overlay Into Every Webpage Without Breaking Any of ThemNextOAuth in a Chrome Extension Is Not the Same as in a Web App

Related articles

What I Learned Publishing My First Chrome Extension
Chrome Extensionschrome extensionschrome web store

What I Learned Publishing My First Chrome Extension

What I Learned Publishing My First Chrome Extension Publishing my first Chrome extension felt different from pushing a normal web app. With a web app, I can deploy quietly, refresh the page, fix something, deploy again, and pretend the first version never happened. A Chrome extension feels more public. There is a...

9 min readRead more
Why Permission Design Is a Product Feature
Chrome Extensionschrome extensionspermissions

Why Permission Design Is a Product Feature

Permissions are not just a technical detail. They are one of the first product decisions a user sees. That is especially true for browser extensions. A landing page can be polished, the icon can look trustworthy, and the feature can solve a real problem. But if Chrome shows a permission warning that sounds broader...

5 min readRead more
I replaced chrome://history — here's why I skipped the AI everyone else would've reached for
Chrome Extensionsbrowser memorychrome history replacement

I replaced chrome://history — here's why I skipped the AI everyone else would've reached for

I replaced chrome://history — here's why I skipped the AI everyone else would've reached for If you tell someone you are building a smarter browser history page, the AI idea arrives almost immediately. Summarize my tabs. Ask my history questions. Name my sessions. Turn yesterday's browsing into a neat report. It is an...

5 min readRead more

On this page

  • The first version looked too much like a marketplace
  • The data model came before the UI
  • The card had to be quiet
  • I avoided ratings on purpose
  • Search needed to work like memory, not like Google
  • Filters are useful only when they reduce doubt
  • The detail page does the trust work
  • Next.js was useful because every extension needs its own URL
  • The install button needed a fallback
  • I added JSON-LD, but kept it boring
  • The homepage section had to stay small
  • What I learned from the Chrome Web Store itself
  • The final page felt smaller, and that made it better
  • The checklist I would use again
  • References