Is there a way to use Materialize CDN with Next.js? - css

I am building a Next.js application and would like to use Materialize CDN with it. In React, I would just add the CDN links in the public/index.html file and would be good to go. Next doesn't seem to have that and I am stuck on how to do this.
I have tried with
npm install materialize-css#next --save
And I import it into the pages/_app.tsx like so:
import 'materialize-css/dist/css/materialize.min.css';
import 'materialize-css'
When it is just the first import alone, it works well, but when I import the second one to add JS, it throws me this error:
Server Error
ReferenceError: window is not defined
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Also, with this kind of import, I can't make any customization to the CSS which is why I would like to use the CDN. I have been at it for days with no luck.

Using Materialize via CDN
As Daniel mentioned in his answer, you can add the CDN links in your custom _document, so both CSS and JavaScript get properly loaded in the browser.
// /pages/_document.js
class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
/>
</Head>
<body>
<Main />
<NextScript />
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</body>
</Html>
);
}
}
Because Materialize uses Web APIs internally, you'll need to access the Materialize instance attached to the window inside a useEffect. This prevents the ReferenceError: window is not defined error when the page gets pre-rendered on the server by Next.js.
Here's a small example of how to use Materialize in a component that renders a carousel.
const MaterializeCarousel = () => {
useEffect(() => {
const elems = document.querySelectorAll('.carousel');
const instances = window.M.Carousel.init(elems);
}, []);
return (
<div className="carousel">
<a className="carousel-item" href="#one!"><img src="https://lorempixel.com/250/250/nature/1" /></a>
<a className="carousel-item" href="#two!"><img src="https://lorempixel.com/250/250/nature/2" /></a>
<a className="carousel-item" href="#three!"><img src="https://lorempixel.com/250/250/nature/3" /></a>
<a className="carousel-item" href="#four!"><img src="https://lorempixel.com/250/250/nature/4" /></a>
<a className="carousel-item" href="#five!"><img src="https://lorempixel.com/250/250/nature/5" /></a>
</div>
);
};
Using Materialize via npm package
Another option is to use to library through its npm package materialize-css.
You first need to import the global CSS, as you did, in _app.
import 'materialize-css/dist/css/materialize.min.css';
The JavaScript code can't be loaded here, as mentioned earlier it utilizes Web APIs that don't work on the server.
Instead, taking the same carousel component as an example, you should dynamically import materialize-css inside a useEffect so it's only loaded on the client-side.
const MaterializeCarousel = () => {
useEffect(() => {
const init = async () => {
const M = await import('materialize-css');
const elems = document.querySelectorAll('.carousel');
const instances = M.Carousel.init(elems);
};
init();
}, []);
return (
<div className="carousel">
<a className="carousel-item" href="#one!"><img src="https://lorempixel.com/250/250/nature/1" /></a>
<a className="carousel-item" href="#two!"><img src="https://lorempixel.com/250/250/nature/2" /></a>
<a className="carousel-item" href="#three!"><img src="https://lorempixel.com/250/250/nature/3" /></a>
<a className="carousel-item" href="#four!"><img src="https://lorempixel.com/250/250/nature/4" /></a>
<a className="carousel-item" href="#five!"><img src="https://lorempixel.com/250/250/nature/5" /></a>
</div>
);
};

Instead of doing it in the _app.tsx do it in a _document.js file. This file is where you augment the html and body tags in NextJS projects. It's important to note that it has to be a .js file, not .tsx.
This file is not present in your project by default and it is auto-generated by nextJS, but you can create it in the pages folder to override the default one and import the CDN there.
You have the _document.js template and more information in the official documentation.

Related

Vuetify 3 - Cannot Import Async Components

I know this isn't the best time to use vuetify 3 but, i did. Hoping everything goes well until i found out that a lot of components are still missing.
Now, I am having a problem importing a lazy component using defineAsyncComponent.
my DOM doesn't seem to recognize async components.
I don't know if it is a vuetify error but my project is made out of vuetify so I was suspecting it was the problem.
Below is my code in dashboardACtionsLayout.vue
<template>
<div>
<div class="d-flex mb-3 mt-2">
<add-customer-modal />
</div>
</div>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
components: {
addCustomerModal: defineAsyncComponent(() =>
import("#/components/customer/addCustomerModal.vue")
);
}
</script>
<style lang="scss" scoped></style>
and this is the error i am getting in my console:
Use defineAsyncComponent like below.
<template>
<div>
<div class="d-flex mb-3 mt-2">
<add-customer-modal />
</div>
</div>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const AddCustomerModal = defineAsyncComponent(() =>
import('#/components/customer/addCustomerModal.vue')
)
</script>
<style lang="scss" scoped></style>
note:
The name of your component is addCustomerModal, while vue's recommendation is that the beginning of all words should be capitalized, like AddCustomerModal.
It is now fixed as of #m kh answer. But when I try to register two components using this code; `
const AddCustomerModal = defineAsyncComponent(() =>
import("#/components/customer/addCustomerModal.vue")
);
const CustomersModal = defineAsyncComponent(() => {
import("#/components/customer/customersModal.vue");
});
` I will get an error like this

How to use asset URLs in style binding with Vite

I want to show a background image from my assets folder. When I use an image tag, the image is shown properly, so the image is well placed, but throws a 404 when I use the background-image style. Any idea about what is happening?. I am using Vue 3 with TypeScript and Vite 2.
This does not resolve the URL:
<div style="background-image: url(./assets/img/header.png)"
></div>
But this does:
<img src="./assets/img/header.png" alt="Header" />
The URL needs to be resolved with import in <script>. #vue/compiler-sfc does not automatically resolve the URLs in <div>.style, but it does for <img>.src, which is why your second example works correctly.
Solution
Use the import keyword in a <script> block to expose the resolved image URL to the template:
<script setup>
import imagePath from '#/assets/logo.svg'
</script>
<template>
<div class="logo" :style="{ backgroundImage: `url(${imagePath})` }"></div>
</template>
<style>
.logo {
height: 400px;
width: 400px;
}
</style>
demo
This is due to vite can't handle alias by default, so we need to set up an alias in vite config file.
there is no need to setup the import image in script tag.
just put the below code in vite.config.js file
import { defineConfig } from "vite";
import vue from "#vitejs/plugin-vue";
import path from "path";
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"#": path.resolve(__dirname, "/src"),
"~#": path.resolve(__dirname, "/src"),
},
},
});

Load Vue 3 Component inside mounted div and pass PHP data

I'm currently working on a new project where I have to integrate Vue 3 in a large Symfony/Drupal project.
The project already contains a lot of PHP code and actually I don't want to refactor too much to begin with.
Well I tried setting up a very small piece of Vue code to see how I could start working on the rest of the code. Actually I just want some PHP code to be transferred from index.html.twig to the sidebar.vue file. I also work with Webpack Encore by the way from Symfony. I read that I could use Vue components to achieve this but my components are not loaded inside my <div id="app"></div>. Or atleast not how I want them to load.
webpack.config.js (Webpack Encore)
var Encore = require('#symfony/webpack-encore');
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
.setOutputPath('webroot/public/build/')
.setPublicPath('/public/build')
.addEntry('main', './vue/src/main.js')
.splitEntryChunks()
.enableSingleRuntimeChunk()
.cleanupOutputBeforeBuild()
.enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
.enableVersioning(Encore.isProduction())
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = 3;
})
.enableSassLoader()
.enablePostCssLoader()
// enables Vue
.enableVueLoader(() => {}, {
version: 3,
runtimeCompilerBuild: false,
});
;
module.exports = Encore.getWebpackConfig();
main.js
import { createApp } from 'vue';
import Sidebar from './components/sidebar';
const app = createApp({})
app.component('sidebar', Sidebar);
app.mount("#app");
sidebar.vue
<template>
<h1>Sidebar</h1>
</template>
<script>
export default {
name: 'Sidebar',
};
</script>
<style lang="scss" module>
</style>
index.html.twig
<div id="app"> <!-- The vue #app is loaded -->
<sidebar></sidebar> <!-- This is not loading -->
</div>
<!-- If it's loading I want to setup something like this -->
<div id="app"> <!-- The vue #app is loaded -->
<sidebar :item="{{ $item }}"></sidebar> <!-- This is not loading -->
</div>
{{ encore_entry_script_tags('main') }}
So how can I make <sidebar></sidebar> to load inside the HTML/Twig file? In the next step I would like to pass some PHP data on the <sidebar> component so I can read it inside the sidebar.vue file. Something like: <sidebar :item="{{ $item }}"></sidebar>
I'm not entirely sure if this is possible with my current setup but I would love to see it work like this or in a similar way.
It seems like I'll have to use the runtimeCompilerBuild. That solves the problem. When false Vue can only be used with single file components which performs better but is less suitable for my application at the moment.
.enableVueLoader(() => {}, {
version: 3,
runtimeCompilerBuild: true,
});
Instead of
runtimeCompilerBuild: false;
In your main.js do :
app.mount("sidebar");
instead of
app.mount("#app");

Issue with NextJS navigation

I am getting the following error when I navigate to Gallery and Contact Link, except when I click on home link ('/').
head-manager.js:2
Uncaught (in promise) TypeError: Cannot read property 'join' of undefined
at head-manager.js:2
Below is my header.js component:
import Link from 'next/link'
import { COLORS, FONT_SIZE } from '../theme/constants'
const Header = () => {
return (
<header>
<Link href='/'>
<a className='site-name'>Name</a>
</Link>
<nav>
<Link href='/'>
<a>Home</a>
</Link>
<Link href='/gallery'>
<a>Gallery</a>
</Link>
<Link href='/contact'>
<a>Contact</a>
</Link>
</nav>
</header>
)
}
export default Header
I don't think the issue is in the above component, but possibly in your _document.js, _app.js, or relevant page that is trying to update title, or other Head properties. You will see this error if the property you are using to set the value does not exist:
if props.myUndefinedProperty does not exist below, you will see the:
head-manager.js:2 Uncaught (in promise) TypeError: Cannot read property 'join' of undefined
import Head from 'next/head';
...
return (
...
<Head>
<title>{props.myUndefinedProperty}</title>
</Head>
I found the issue. I am using getStaticProps to fetch data from sitedata.json in index.js file and passing contents of file as props through Layout component. Thats how my header and Head components receive site title and description info. But I am not passing them through the Gallery and About page files. I figured if I get them once from index file it would be able to use them globally, but that is not the case. I have to fetch that data using getStaticProps on every page file, which got rid of error.

angularjs and brackets live preview: The simplest code doesn't work

I'm a newbie to angularJS and I'm trying to make the simple thing to work but I fail.
HTML:
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="main.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<link rel="stylesheet" href="main.css">
</head>
<body ng-app="app" ng-strict-di>
<div class="test" ng-controller="testSrc">
<p> {{ blah }} </p>
<img ng-src="{{ URLImage }}"/>
<button class="btn btn-sucess" ng-click="goYahoo()">Yahoo</button>
<button class="btn btn-sucess" ng-click="goGoogle()">Google</button>
</div>
</body>
</html>
JS:
var app = angular.module("app", []);
app.controller('testSrc', ['$scope,$location', function ($scope, $location) {
"use strict";
$scope.blah = "blah blah";
$scope.URLImage = 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/512px-Google_%22G%22_Logo.svg.png';
$scope.goGoogle = function () { $location.url('localhost:58164/g'); };
$scope.goYahoo = function () { $location.url('localhost:58164/y'); };
}]);
app.config(function ($routeProvider) {
"use strict";
$routeProvider
.when('/', {
templateUrl : 'index.html'
})
.when('/g', {
templateUrl : 'http://www.google.com'
})
.when('/y', {
templateUrl : 'http://www.yahoo.com'
});
});
The code has passed all lint warnings. I use the live preview of brackets that opens up Internet explorer. I encounter 3 issues:
1) {{ blah }} does not translate into blah blah, I see {{ blah }} as if the js is ignored. same for the ng-src of the img. It is ignored and I don't see the image.
2) the 2 buttons are not colored in green as I expect it to be from the bootstrap css. I tried it in jsFiddle and did see them as green but when I tried again now, they were regular, not sure what did I do wrong.
3) Routing: I want to browse to Google/Yahoo when navigating to specific url g and y using the 2 buttons. When I open the live preview, the url is: http://127.0.0.1:58164/index.html so how can I route this correctly? http://127.0.0.1:58164/index.html/g won't work.
I'm kinda lost here, not sure if the problem is my code, the browser of the live preview though the JS didn't work also in jsFiddle...
1) You're injecting the dependencies into your controller incorrectly - you need a string for each argument of the function. It's an easy mistake to make!
app.controller('testSrc', ['$scope,$location', function ($scope, $location) { // Wrong
app.controller('testSrc', ['$scope', '$location', function ($scope, $location) { // Right
2) You've misspelled the class name in your buttons. 'sucess' should be 'success'.
<button class="btn btn-sucess" ng-click="goYahoo()">Yahoo</button>
^^^^^^
3) There's numerous things wrong with your routing:
You haven't included the ngRoute module in your HTML - it hasn't been included in the base Angular package for a long time now.
Once that's done, you need to add it as a dependency: var app = angular.module("app", ["ngRoute"]); and add an ng-view tag to your HTML.
By default, the router will use 'hashbangs' for the URL - so the URL would be something along the lines of `http://127.0.0.1:58164/index.html#/g. If this isn't acceptable for you, I'd look into HTML5 mode.
All that being said, I don't think ngRoute will help you accomplish what you're trying to do. The router is designed to route through the pages of your app, not to external sites, so trying to load HTML from another domain probably won't work.
I'd recommend running through the official Angular tutorial if you haven't already - it covers all this stuff quite well. I'd also recommend Shaping Up With Angular.js on Code School, if you would prefer something a bit more hands-on.

Resources