Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions apps/frontend/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export default defineNuxtConfig({
"@nuxtjs/color-mode",
"@vueuse/nuxt",
"@regle/nuxt",
"@nuxtjs/sitemap",
"nuxt-schema-org",
"nuxt-og-image",
"vue-sonner/nuxt",
"@nuxtjs/i18n",
Expand Down Expand Up @@ -60,6 +62,25 @@ export default defineNuxtConfig({
},
},
},
// SEO site config (consumed by nuxt-site-config — pulled in by sitemap/schema/og).
// Single fixed domain — absolute URL so sitemap + schema resolve correctly.
site: {
url: "https://solitar.link",
name: "Solitar",
},
// Global <html lang> (H3). No title template is applied (seo-utils intentionally
// not used), so page titles render exactly as authored (H4).
app: {
head: {
htmlAttrs: { lang: "en" },
},
},
// Sitemap (C1): single flat /sitemap.xml (one locale — skip i18n index split);
// only public pages, exclude utility/dynamic routes.
sitemap: {
autoI18n: false,
exclude: ["/settings", "/unlock"],
},
// Build
nitro: {
prerender: {
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
},
"devDependencies": {
"@iconify-json/tabler": "catalog:frontend",
"@nuxtjs/sitemap": "catalog:frontend",
"@takumi-rs/core": "catalog:frontend",
"@takumi-rs/wasm": "catalog:frontend",
"@unocss/nuxt": "catalog:frontend",
"@vueuse/core": "catalog:frontend",
"@vueuse/nuxt": "catalog:frontend",
"nuxt-og-image": "catalog:frontend",
"nuxt-schema-org": "catalog:frontend",
"oxlint": "catalog:lint",
"oxlint-tsgolint": "catalog:lint",
"unocss": "catalog:frontend",
Expand Down
35 changes: 35 additions & 0 deletions apps/frontend/src/app/app.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
<script setup lang="ts">
const route = useRoute();
const SITE_URL = "https://solitar.link";
// Self-referencing canonical (H1) — hardcoded origin so it's always absolute
// and correct regardless of request host.
const canonicalUrl = computed(() => {
const path = route.path === "/" ? "/" : route.path.replace(/\/+$/, "");
return `${SITE_URL}${path}`;
});

// Canonical link (H1)
useHead({
link: [{ rel: "canonical", href: canonicalUrl }],
});

// Global OG defaults (M2)
useSeoMeta({
ogType: "website",
ogSiteName: "Solitar",
ogUrl: canonicalUrl,
});

// Global structured data (H2): Organization + WebSite
useSchemaOrg([
defineOrganization({
name: "Solitar",
logo: "/icon-light.svg",
sameAs: ["https://github.com/solitar-dev/code"],
}),
defineWebSite({
name: "Solitar",
}),
]);
</script>

<template>
<Toaster
position="top-right"
Expand Down
3 changes: 3 additions & 0 deletions apps/frontend/src/app/pages/[shortCode].vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<script setup lang="ts">
import { parseURL } from "ufo";

// Redirect resolver route — never index (M1). Does not alter redirect logic.
useSeoMeta({ robots: "noindex, follow" });

const route = useRoute();
const { $api } = useNuxtApp();
const urlRepository = repository($api);
Expand Down
10 changes: 10 additions & 0 deletions apps/frontend/src/app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ useSeoMeta({
ogDescription: $t("site.url_shortener.description"),
twitterDescription: $t("site.url_shortener.description"),
});

// Structured data (H2): the app itself
useSchemaOrg([
defineSoftwareApp({
name: "Solitar",
applicationCategory: "UtilitiesApplication",
operatingSystem: "Web",
offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
}),
]);
</script>

<template>
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/app/pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ useSeoMeta({
description: $t("site.settings.description"),
ogDescription: $t("site.settings.description"),
twitterDescription: $t("site.settings.description"),
robots: "noindex, follow",
});
</script>

Expand Down
3 changes: 3 additions & 0 deletions apps/frontend/src/app/pages/unlock.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<script setup lang="ts">
// Utility page — keep out of the index (M1)
useSeoMeta({ robots: "noindex, follow" });

const route = useRoute();

const shortCode = route.query.c!.toString();
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
"url_shortener": {
"title": "url shortener - solitar",
"og_title": "URL Shortener - Solitar",
"description": "Create short URLS for your website and social media pages in seconds"
"description": "Create short URLs for your website and social media pages in seconds"
},
"qr_generator": {
"title": "qr code generator - solitar",
"og_title": "QR Code Generator - Solitar",
"description": "Create QR code for your URLS in seconds, save and share it immediately"
"description": "Create QR code for your URLs in seconds, save and share it immediately"
},
"settings": {
"title": "settings - solitar",
Expand Down
16 changes: 16 additions & 0 deletions apps/frontend/src/public/llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Solitar

> Open-source URL shortener and QR code generator. Instant redirects, REST API,
> link expiry, security verification, and privacy-focused analytics. MIT licensed,
> self-hostable.

## Pages

- [URL shortener (home)](https://solitar.link/): Shorten long links in seconds.
- [QR code generator](https://solitar.link/qr): Create and download QR codes for any URL.

## Developer

- [API documentation](https://docs.solitar.link): REST API for automating link creation.
- [Source code (GitHub, MIT)](https://github.com/solitar-dev/code): Audit or self-host Solitar.
- [Status](https://status.solitar.link): Service status.
Loading