From 2f5ed84e2bacb12432242c0135957feee95ca62d Mon Sep 17 00:00:00 2001 From: Kevin MacMartin Date: Wed, 3 Apr 2024 22:35:43 -0400 Subject: [PATCH] Handle metadata in the database for a few reasons: the method is the same between traditional and vue, the user has control over the values, we can create dynamic titles a bit more easily, and with vue the values are populated before the SPA loads so search engines can pick it up more easily --- .env.example | 1 - app/Dashboard.php | 6 +++ app/Http/Controllers/ApiController.php | 6 +++ app/Models/Meta.php | 54 +++++++++++++++++++ .../2024_04_03_164823_create_meta_table.php | 31 +++++++++++ database/seeders/DatabaseSeeder.php | 2 +- database/seeders/MetaSeeder.php | 53 ++++++++++++++++++ resources/components/pages/contact.vue | 3 -- resources/components/pages/error404.vue | 9 +--- resources/components/pages/home.vue | 6 --- resources/js/app.js | 9 ++++ resources/js/mixins/base-page.js | 38 ++++++------- resources/views/errors/404.blade.php | 4 +- resources/views/templates/base.blade.php | 28 ++++++---- resources/views/templates/dashboard.blade.php | 2 +- resources/views/templates/public.blade.php | 1 - routes/api.php | 1 + .../resources/views/pages/blog.blade.php | 2 +- .../resources/views/pages/contact.blade.php | 2 +- 19 files changed, 204 insertions(+), 54 deletions(-) create mode 100644 app/Models/Meta.php create mode 100644 database/migrations/2024_04_03_164823_create_meta_table.php create mode 100644 database/seeders/MetaSeeder.php diff --git a/.env.example b/.env.example index 2bcabba..a890d88 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ DEFAULT_LANGUAGE=en APP_NAME='Hypothetical' -APP_DESC='A website template' APP_ENV=local APP_KEY= APP_DEBUG=true diff --git a/app/Dashboard.php b/app/Dashboard.php index b6d3b6b..593e3c6 100644 --- a/app/Dashboard.php +++ b/app/Dashboard.php @@ -10,6 +10,12 @@ class Dashboard * @return array */ public static $menu = [ + [ + 'title' => 'Metadata', + 'type' => 'edit', + 'model' => 'meta' + ], + [ 'title' => 'Blog', 'type' => 'edit', diff --git a/app/Http/Controllers/ApiController.php b/app/Http/Controllers/ApiController.php index c8bdd90..7ec8a0a 100644 --- a/app/Http/Controllers/ApiController.php +++ b/app/Http/Controllers/ApiController.php @@ -6,10 +6,16 @@ use Newsletter; use App\Models\Blog; use App\Models\Contact; use App\Models\Subscriptions; +use App\Models\Meta; use Illuminate\Http\Request; class ApiController extends Controller { + public function getMeta($path = null) + { + return Meta::getData($path); + } + public function getBlogEntries() { return Blog::getBlogEntries(); diff --git a/app/Models/Meta.php b/app/Models/Meta.php new file mode 100644 index 0000000..45e5950 --- /dev/null +++ b/app/Models/Meta.php @@ -0,0 +1,54 @@ + 'path', 'required' => true, 'unique' => true, 'type' => 'string' ], + [ 'name' => 'title', 'required' => true, 'unique' => false, 'type' => 'string' ], + [ 'name' => 'description', 'required' => true, 'unique' => false, 'type' => 'text' ], + [ 'name' => 'keywords', 'required' => true, 'unique' => false, 'type' => 'string' ] + ]; + + public static function getData($path) + { + if (!preg_match('/^\//', $path)) { + $path = "/$path"; + } + + if (preg_match('/^\/(dashboard|login|register)/', $path)) { + $page = [ + 'title' => 'Dashboard' . ' | ' . env('APP_NAME'), + 'description' => '', + 'keywords' => '' + ]; + } else { + $page = self::select('title', 'description', 'keywords')->where('path', "$path")->first(); + + if ($page == null) { + $page = [ + 'title' => 'Page Not Found' . ' | ' . env('APP_NAME'), + 'description' => 'The requested page cannot be found', + 'keywords' => '' + ]; + } else { + $page['title'] = $page['title'] . ' | ' . env('APP_NAME'); + } + } + + return $page; + } +} diff --git a/database/migrations/2024_04_03_164823_create_meta_table.php b/database/migrations/2024_04_03_164823_create_meta_table.php new file mode 100644 index 0000000..640e1fa --- /dev/null +++ b/database/migrations/2024_04_03_164823_create_meta_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('path')->nullable(); + $table->text('title')->nullable(); + $table->text('description')->nullable(); + $table->text('keywords')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('meta'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index cdde73c..5b31783 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -13,6 +13,6 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - + $this->call(MetaSeeder::class); } } diff --git a/database/seeders/MetaSeeder.php b/database/seeders/MetaSeeder.php new file mode 100644 index 0000000..1ac7080 --- /dev/null +++ b/database/seeders/MetaSeeder.php @@ -0,0 +1,53 @@ +delete(); + + // Page metadata + $pages = [ + [ + 'path' => '/', + 'title' => 'Home', + 'description' => '', + 'keywords' => '' + ], + + [ + 'path' => '/blog', + 'title' => 'Blog', + 'description' => '', + 'keywords' => '' + ], + + [ + 'path' => '/contact', + 'title' => 'Contact', + 'description' => '', + 'keywords' => '' + ] + ]; + + foreach ($pages as $page) { + Meta::create([ + 'path' => $page['path'], + 'title' => $page['title'], + 'description' => $page['description'], + 'keywords' => $page['keywords'] + ]); + } + } +} diff --git a/resources/components/pages/contact.vue b/resources/components/pages/contact.vue index 9bc6556..6c41bd5 100644 --- a/resources/components/pages/contact.vue +++ b/resources/components/pages/contact.vue @@ -69,9 +69,6 @@ data() { return { - metaTitle: "Contact", - metaDescription: "Contact Us", - metaKeywords: "contact", submitting: false, errorCount: 0, submitSuccess: false, diff --git a/resources/components/pages/error404.vue b/resources/components/pages/error404.vue index 9554c15..296ff19 100644 --- a/resources/components/pages/error404.vue +++ b/resources/components/pages/error404.vue @@ -10,13 +10,6 @@ export default { mixins: [ BasePageMixin - ], - - data() { - return { - metaTitle: "Page Not Found", - metaDescription: "The requested page cannot be found" - }; - } + ] }; diff --git a/resources/components/pages/home.vue b/resources/components/pages/home.vue index 3306de4..5371839 100644 --- a/resources/components/pages/home.vue +++ b/resources/components/pages/home.vue @@ -15,12 +15,6 @@ components: { "subscription-form": SubscriptionFormSection - }, - - data() { - return { - metaKeywords: "home" - }; } }; diff --git a/resources/js/app.js b/resources/js/app.js index 86f6a2b..1082660 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -75,6 +75,7 @@ const store = createStore({ appLang: env.appLang, appDefaultLang: env.appDefaultLang, firstLoad: true, + firstPage: true, lastPath: "", supportsWebP: null }, @@ -96,6 +97,10 @@ const store = createStore({ return state.firstLoad; }, + getFirstPage: state => { + return state.firstPage; + }, + getLastPath: state => { return state.lastPath; }, @@ -115,6 +120,10 @@ const store = createStore({ state.firstLoad = value; }, + setFirstPage(state, value) { + state.firstPage = value; + }, + setLastPath(state, value) { state.lastPath = value; }, diff --git a/resources/js/mixins/base-page.js b/resources/js/mixins/base-page.js index 80d2e69..a633e93 100644 --- a/resources/js/mixins/base-page.js +++ b/resources/js/mixins/base-page.js @@ -1,10 +1,6 @@ export default { data() { return { - metaTitle: "", - metaDescription: "", - metaKeywords: "", - metaTags: { "title": [ "name", "title" ], "description": [ "name", "description" ], @@ -21,14 +17,6 @@ export default { }, computed: { - pageTitle() { - return this.metaTitle === "" ? env.appName : `${this.metaTitle} | ${env.appName}`; - }, - - pageDescription() { - return this.metaDescription === "" ? env.appDesc : this.metaDescription; - }, - fullPath() { return document.location.origin + this.$route.path; } @@ -43,22 +31,22 @@ export default { } }, - updateMetaData() { + updateMetadata(meta) { let metaContent; - document.title = this.pageTitle; + document.title = meta.title; $("link[rel=canonical]").attr("href", this.fullPath); Object.keys(this.metaTags).forEach((name) => { switch (this.metaTags[name][1]) { case "title": - metaContent = this.pageTitle; + metaContent = meta.title; break; case "description": - metaContent = this.pageDescription; + metaContent = meta.description; break; case "keywords": - metaContent = this.metaKeywords; + metaContent = meta.keywords; break; case "url": metaContent = this.fullPath; @@ -69,10 +57,24 @@ export default { this.updateMetaTag(this.metaTags[name][0], name, metaContent); }); + }, + + fetchMetadata() { + this.$http.get(`/api/meta${this.$route.path}${env.apiToken}`).then((response) => { + this.updateMetadata(response.data); + }).catch((error) => { + console.log("error fetching metadata"); + this.updateMetadata({ title: appName, description: "", keywords: "" }); + }); } }, created() { - this.updateMetaData(); + // Don't fetch metadata on the first page load as this is handled by the page render + if (this.$store.getters.getFirstPage) { + this.$store.commit("setFirstPage", false); + } else { + this.fetchMetadata(); + } } }; diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php index f9abf7c..ddf23a8 100644 --- a/resources/views/errors/404.blade.php +++ b/resources/views/errors/404.blade.php @@ -1,3 +1 @@ -@extends('templates.error', [ - 'title' => 'Page Not Found' -]) +@extends('templates.error') diff --git a/resources/views/templates/base.blade.php b/resources/views/templates/base.blade.php index c91324f..598b841 100644 --- a/resources/views/templates/base.blade.php +++ b/resources/views/templates/base.blade.php @@ -1,8 +1,15 @@ @php - $page_title = (isset($title) ? $title . ' - ' : '') . env('APP_NAME'); + // Determine whether the device is mobile $device_mobile = !is_null(Request::header('User-Agent')) && (preg_match('/Mobi/', Request::header('User-Agent')) || preg_match('/iP(hone|ad|od);/', Request::header('User-Agent'))); + + // If an overridden title has been set (error pages) then use that, otherwise use the Meta model to populate metadata + if (isset($title)) { + $meta = [ 'title' => $title . ' - ' . env('APP_NAME'), 'description' => '', 'keywords' => '' ]; + } else { + $meta = App\Models\Meta::getData(Request::path()); + } @endphp @@ -11,22 +18,23 @@ - {{ $page_title }} + {{ $meta['title'] }} - - - - + + + + + - - + + - - + + diff --git a/resources/views/templates/dashboard.blade.php b/resources/views/templates/dashboard.blade.php index a821d7e..b0fea94 100644 --- a/resources/views/templates/dashboard.blade.php +++ b/resources/views/templates/dashboard.blade.php @@ -1,4 +1,4 @@ -@extends('templates.base', [ 'title' => 'Dashboard' ]) +@extends('templates.base') @php $current_page = preg_match('/\/settings$/', Request::url()) ? 'settings' : preg_replace([ '/https?:\/\/[^\/]*\/dashboard\/[^\/]*\//', '/\/.*/' ], [ '', '' ], Request::url()); diff --git a/resources/views/templates/public.blade.php b/resources/views/templates/public.blade.php index dd557d4..55e03de 100644 --- a/resources/views/templates/public.blade.php +++ b/resources/views/templates/public.blade.php @@ -7,7 +7,6 @@